Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Nikita Panov <panov.nikita@huawei.com>
To: <catalin.marinas@arm.com>, <akpm@linux-foundation.org>,
	<david@kernel.org>, <ljs@kernel.org>, <vbabka@kernel.org>,
	<cl@gentwo.org>, <linux@armlinux.org.uk>, <will@kernel.org>,
	<mark.rutland@arm.com>, <liam@infradead.org>, <rppt@kernel.org>,
	<surenb@google.com>, <mhocko@suse.com>
Cc: <linux-mm@kvack.org>, <linux-kernel@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>,
	<wangkefeng.wang@huawei.com>, <artem.kuzin@huawei.com>,
	<panov.nikita@huawei.com>
Subject: [RFC PATCH 09/18] mm: add replicas allocation support for vmalloc
Date: Fri, 28 Aug 2026 00:11:49 +0800	[thread overview]
Message-ID: <20260827161158.3618409-10-panov.nikita@huawei.com> (raw)
In-Reply-To: <20260827161158.3618409-1-panov.nikita@huawei.com>

In order to support generic vmalloc, several modifications are required.
If the allocated region belongs to the non-replicated part of the table,
then the normal vmalloc mechanism is suitable.
If the allocated region belongs to the replicated part of the table,
a replicated table for each replica node must be created for this memory.
This region might be replicated after its initialization,
for example, to support replication of text and ro-data
for loadable kernel modules.

Acked-by: Alexander Grubnikov <alexander.grubnikov@huawei.com>
Acked-by: Ilya Hanov <ilya.hanov@huawei-partners.com>
Acked-by: Denis Darvish <darvish.denis@huawei.com>
Co-developed-by: Artem Kuzin <artem.kuzin@huawei.com>
Signed-off-by: Artem Kuzin <artem.kuzin@huawei.com>
Co-developed-by: Nikita Panov <panov.nikita@huawei.com>
Signed-off-by: Nikita Panov <panov.nikita@huawei.com>
---
 include/linux/mm.h      |   2 +
 include/linux/vmalloc.h |  18 ++
 mm/memory.c             |  61 ++++++
 mm/vmalloc.c            | 454 +++++++++++++++++++++++++++++++++-------
 4 files changed, 461 insertions(+), 74 deletions(-)

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 95213a81907c..8a7083c02203 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1885,6 +1885,8 @@ int region_intersects(resource_size_t offset, size_t size, unsigned long flags,
 struct page *vmalloc_to_page(const void *addr);
 unsigned long vmalloc_to_pfn(const void *addr);
 
+struct page *walk_to_page_node(int nid, const void *addr);
+
 /*
  * Determine if an address is within the vmalloc range
  *
diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h
index aed121d729b0..44eda2175a28 100644
--- a/include/linux/vmalloc.h
+++ b/include/linux/vmalloc.h
@@ -31,6 +31,10 @@ struct iov_iter;		/* in uio.h */
 #define VM_MAP_PUT_PAGES	0x00000200	/* put pages and free array in vfree */
 #define VM_ALLOW_HUGE_VMAP	0x00000400      /* Allow for huge pages on archs with HAVE_ARCH_HUGE_VMALLOC */
 
+#ifdef CONFIG_KERNEL_REPLICATION
+#define VM_NUMA_SHARED		0x00002000	/* Pages shared between per-NUMA node TT*/
+#endif
+
 #if (defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)) && \
 	!defined(CONFIG_KASAN_VMALLOC)
 #define VM_DEFER_KMEMLEAK	0x00000800	/* defer kmemleak object creation */
@@ -66,6 +70,10 @@ struct vm_struct {
 	phys_addr_t		phys_addr;
 	const void		*caller;
 	unsigned long		requested_size;
+#ifdef CONFIG_KERNEL_REPLICATION
+	int			node;
+	bool			replicated;
+#endif
 };
 
 struct vmap_area {
@@ -177,6 +185,16 @@ extern void *__vmalloc_node_range_noprof(unsigned long size, unsigned long align
 			const void *caller) __alloc_size(1);
 #define __vmalloc_node_range(...)	alloc_hooks(__vmalloc_node_range_noprof(__VA_ARGS__))
 
+#ifdef CONFIG_KERNEL_REPLICATION
+ /*
+  * DO NOT USE this function if you don't understand what it is doing
+  * Use only in pair with vmalloc(vm_flags|=VM_NUMA_SHARED)
+  */
+int __vmalloc_node_replicate_range(const void *addr, gfp_t gfp_mask,
+		pgprot_t prot, unsigned long vm_flags);
+void vunmap_range_replicas(unsigned long addr, unsigned long end);
+#endif
+
 void *__vmalloc_node_noprof(unsigned long size, unsigned long align, gfp_t gfp_mask,
 		int node, const void *caller) __alloc_size(1);
 #define __vmalloc_node(...)	alloc_hooks(__vmalloc_node_noprof(__VA_ARGS__))
diff --git a/mm/memory.c b/mm/memory.c
index 05853e4fb266..49e5a502abd8 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -78,6 +78,7 @@
 #include <linux/sched/sysctl.h>
 #include <linux/pgalloc.h>
 #include <linux/uaccess.h>
+#include <linux/numa_kernel_replication.h>
 
 #include <trace/events/kmem.h>
 
@@ -7810,4 +7811,64 @@ void vma_pgtable_walk_end(struct vm_area_struct *vma)
 {
 	if (is_vm_hugetlb_page(vma))
 		hugetlb_vma_unlock_read(vma);
+
+}
+/**
+ * Walk in replicated tranlation table specified by nid.
+ * If kernel replication is disabled or text is not replicated yet,
+ * value of nid is not used
+ */
+struct page *walk_to_page_node(int nid, const void *vmalloc_addr)
+{
+	unsigned long addr = (unsigned long)vmalloc_addr;
+	struct page *page = NULL;
+	pgd_t *pgd;
+	p4d_t *p4d;
+	pud_t *pud;
+	pmd_t *pmd;
+	pte_t *ptep, pte;
+
+	if (is_text_replicated())
+		pgd = pgd_offset_pgd(per_node_pgd(&init_mm, nid), addr);
+	else
+		pgd = pgd_offset_pgd(init_mm.pgd, addr);
+
+	if (pgd_none(*pgd))
+		return NULL;
+	if (WARN_ON_ONCE(pgd_leaf(*pgd)))
+		return NULL; /* XXX: no allowance for huge pgd */
+	if (WARN_ON_ONCE(pgd_bad(*pgd)))
+		return NULL;
+
+	p4d = p4d_offset(pgd, addr);
+	if (p4d_none(*p4d))
+		return NULL;
+	if (p4d_leaf(*p4d))
+		return p4d_page(*p4d) + ((addr & ~P4D_MASK) >> PAGE_SHIFT);
+	if (WARN_ON_ONCE(p4d_bad(*p4d)))
+		return NULL;
+
+	pud = pud_offset(p4d, addr);
+	if (pud_none(*pud))
+		return NULL;
+	if (pud_leaf(*pud))
+		return pud_page(*pud) + ((addr & ~PUD_MASK) >> PAGE_SHIFT);
+	if (WARN_ON_ONCE(pud_bad(*pud)))
+		return NULL;
+
+	pmd = pmd_offset(pud, addr);
+	if (pmd_none(*pmd))
+		return NULL;
+	if (pmd_leaf(*pmd))
+		return pmd_page(*pmd) + ((addr & ~PMD_MASK) >> PAGE_SHIFT);
+	if (WARN_ON_ONCE(pmd_bad(*pmd)))
+		return NULL;
+
+	ptep = pte_offset_map(pmd, addr);
+	pte = *ptep;
+	if (pte_present(pte))
+		page = pte_page(pte);
+	pte_unmap(ptep);
+
+	return page;
 }
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index bea9f76ed7e7..da6facf64db8 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -10,6 +10,7 @@
 
 #include <linux/vmalloc.h>
 #include <linux/mm.h>
+#include <linux/numa_kernel_replication.h>
 #include <linux/module.h>
 #include <linux/highmem.h>
 #include <linux/sched/signal.h>
@@ -318,7 +319,7 @@ static int vmap_p4d_range(pgd_t *pgd, unsigned long addr, unsigned long end,
 	return err;
 }
 
-static int vmap_range_noflush(unsigned long addr, unsigned long end,
+static int vmap_range_noflush_pgd(pgd_t *pgtable, unsigned long addr, unsigned long end,
 			phys_addr_t phys_addr, pgprot_t prot,
 			unsigned int max_page_shift)
 {
@@ -337,7 +338,7 @@ static int vmap_range_noflush(unsigned long addr, unsigned long end,
 	BUG_ON(addr >= end);
 
 	start = addr;
-	pgd = pgd_offset_k(addr);
+	pgd = pgd_offset_pgd(pgtable, addr);
 	do {
 		next = pgd_addr_end(addr, end);
 		err = vmap_p4d_range(pgd, addr, next, phys_addr, prot,
@@ -352,6 +353,13 @@ static int vmap_range_noflush(unsigned long addr, unsigned long end,
 	return err;
 }
 
+static int vmap_range_noflush(unsigned long addr, unsigned long end,
+			phys_addr_t phys_addr, pgprot_t prot,
+			unsigned int max_page_shift)
+{
+	return vmap_range_noflush_pgd(init_mm.pgd, addr, end, phys_addr, prot, max_page_shift);
+}
+
 int vmap_page_range(unsigned long addr, unsigned long end,
 		    phys_addr_t phys_addr, pgprot_t prot)
 {
@@ -490,18 +498,17 @@ static void vunmap_p4d_range(pgd_t *pgd, unsigned long addr, unsigned long end,
 }
 
 /*
- * vunmap_range_noflush is similar to vunmap_range, but does not
- * flush caches or TLBs.
+ * __vunmap_range_noflush_pgd is similar to vunmap_range, but does not
+ * flush caches or TLBs, and able to work with pgd granularity.
  *
  * The caller is responsible for calling flush_cache_vmap() before calling
  * this function, and flush_tlb_kernel_range after it has returned
  * successfully (and before the addresses are expected to cause a page fault
  * or be re-mapped for something else, if TLB flushes are being delayed or
  * coalesced).
- *
- * This is an internal function only. Do not use outside mm/.
  */
-void __vunmap_range_noflush(unsigned long start, unsigned long end)
+static void __vunmap_range_noflush_pgd(pgd_t *pgtable,
+		unsigned long start, unsigned long end)
 {
 	unsigned long next;
 	pgd_t *pgd;
@@ -509,7 +516,7 @@ void __vunmap_range_noflush(unsigned long start, unsigned long end)
 	pgtbl_mod_mask mask = 0;
 
 	BUG_ON(addr >= end);
-	pgd = pgd_offset_k(addr);
+	pgd = pgd_offset_pgd(pgtable, addr);
 	do {
 		next = pgd_addr_end(addr, end);
 		if (pgd_bad(*pgd))
@@ -523,6 +530,17 @@ void __vunmap_range_noflush(unsigned long start, unsigned long end)
 		arch_sync_kernel_mappings(start, end);
 }
 
+/*
+ * vunmap_range_noflush is similar to __vunmap_range_noflush_pgd, but works
+ * only with init_mm->pgd.
+ *
+ * This is an internal function only. Do not use outside mm/.
+ */
+void __vunmap_range_noflush(unsigned long start, unsigned long end)
+{
+	__vunmap_range_noflush_pgd(init_mm.pgd, start, end);
+}
+
 void vunmap_range_noflush(unsigned long start, unsigned long end)
 {
 	kmsan_vunmap_range_noflush(start, end);
@@ -545,6 +563,18 @@ void vunmap_range(unsigned long addr, unsigned long end)
 	flush_tlb_kernel_range(addr, end);
 }
 
+#ifdef CONFIG_KERNEL_REPLICATION
+void vunmap_range_replicas(unsigned long addr, unsigned long end)
+{
+	int nid;
+
+	flush_cache_vunmap(addr, end);
+	for_each_memory_node(nid)
+		__vunmap_range_noflush_pgd(init_mm.pgd_numa[nid], addr, end);
+	flush_tlb_kernel_range(addr, end);
+}
+#endif
+
 static int vmap_pages_pte_range(pmd_t *pmd, unsigned long addr,
 		unsigned long end, pgprot_t prot, struct page **pages, int *nr,
 		pgtbl_mod_mask *mask)
@@ -643,7 +673,8 @@ static int vmap_pages_p4d_range(pgd_t *pgd, unsigned long addr,
 	return 0;
 }
 
-static int vmap_small_pages_range_noflush(unsigned long addr, unsigned long end,
+static int vmap_small_pages_range_noflush_pgd(pgd_t *pgtable,
+		unsigned long addr, unsigned long end,
 		pgprot_t prot, struct page **pages)
 {
 	unsigned long start = addr;
@@ -654,7 +685,7 @@ static int vmap_small_pages_range_noflush(unsigned long addr, unsigned long end,
 	pgtbl_mod_mask mask = 0;
 
 	BUG_ON(addr >= end);
-	pgd = pgd_offset_k(addr);
+	pgd = pgd_offset_pgd(pgtable, addr);
 	do {
 		next = pgd_addr_end(addr, end);
 		if (pgd_bad(*pgd))
@@ -671,7 +702,7 @@ static int vmap_small_pages_range_noflush(unsigned long addr, unsigned long end,
 }
 
 /*
- * vmap_pages_range_noflush is similar to vmap_pages_range, but does not
+ * __vmap_pages_range_noflush_pgd is similar to vmap_pages_range, but does not
  * flush caches.
  *
  * The caller is responsible for calling flush_cache_vmap() after this
@@ -679,7 +710,7 @@ static int vmap_small_pages_range_noflush(unsigned long addr, unsigned long end,
  *
  * This is an internal function only. Do not use outside mm/.
  */
-int __vmap_pages_range_noflush(unsigned long addr, unsigned long end,
+static int __vmap_pages_range_noflush_pgd(pgd_t *pgtable, unsigned long addr, unsigned long end,
 		pgprot_t prot, struct page **pages, unsigned int page_shift)
 {
 	unsigned int i, nr = (end - addr) >> PAGE_SHIFT;
@@ -688,12 +719,13 @@ int __vmap_pages_range_noflush(unsigned long addr, unsigned long end,
 
 	if (!IS_ENABLED(CONFIG_HAVE_ARCH_HUGE_VMALLOC) ||
 			page_shift == PAGE_SHIFT)
-		return vmap_small_pages_range_noflush(addr, end, prot, pages);
+		return vmap_small_pages_range_noflush_pgd(pgtable, addr, end,
+				prot, pages);
 
 	for (i = 0; i < nr; i += 1U << (page_shift - PAGE_SHIFT)) {
 		int err;
 
-		err = vmap_range_noflush(addr, addr + (1UL << page_shift),
+		err = vmap_range_noflush_pgd(pgtable, addr, addr + (1UL << page_shift),
 					page_to_phys(pages[i]), prot,
 					page_shift);
 		if (err)
@@ -705,7 +737,7 @@ int __vmap_pages_range_noflush(unsigned long addr, unsigned long end,
 	return 0;
 }
 
-int vmap_pages_range_noflush(unsigned long addr, unsigned long end,
+static int vmap_pages_range_noflush_pgd(pgd_t *pgtable, unsigned long addr, unsigned long end,
 		pgprot_t prot, struct page **pages, unsigned int page_shift,
 		gfp_t gfp_mask)
 {
@@ -714,20 +746,35 @@ int vmap_pages_range_noflush(unsigned long addr, unsigned long end,
 
 	if (ret)
 		return ret;
-	return __vmap_pages_range_noflush(addr, end, prot, pages, page_shift);
+	return __vmap_pages_range_noflush_pgd(pgtable, addr, end, prot, pages, page_shift);
 }
 
-static int __vmap_pages_range(unsigned long addr, unsigned long end,
+int vmap_pages_range_noflush(unsigned long addr, unsigned long end,
+		pgprot_t prot, struct page **pages, unsigned int page_shift,
+		gfp_t gfp_mask)
+{
+	return vmap_pages_range_noflush_pgd(init_mm.pgd, addr, end, prot, pages,
+						page_shift, gfp_mask);
+}
+
+static int __vmap_pages_range_pgd(pgd_t *pgtable, unsigned long addr, unsigned long end,
 		pgprot_t prot, struct page **pages, unsigned int page_shift,
 		gfp_t gfp_mask)
 {
 	int err;
 
-	err = vmap_pages_range_noflush(addr, end, prot, pages, page_shift, gfp_mask);
+	err = vmap_pages_range_noflush_pgd(pgtable, addr, end, prot, pages, page_shift, gfp_mask);
 	flush_cache_vmap(addr, end);
 	return err;
 }
 
+static int __vmap_pages_range(unsigned long addr, unsigned long end,
+		pgprot_t prot, struct page **pages, unsigned int page_shift,
+		gfp_t gfp_mask)
+{
+	return __vmap_pages_range_pgd(init_mm.pgd, addr, end, prot, pages, page_shift, gfp_mask);
+}
+
 /**
  * vmap_pages_range - map pages to a kernel virtual address
  * @addr: start of the VM area to map
@@ -821,57 +868,12 @@ EXPORT_SYMBOL_GPL(is_vmalloc_or_module_addr);
  */
 struct page *vmalloc_to_page(const void *vmalloc_addr)
 {
-	unsigned long addr = (unsigned long) vmalloc_addr;
-	struct page *page = NULL;
-	pgd_t *pgd = pgd_offset_k(addr);
-	p4d_t *p4d;
-	pud_t *pud;
-	pmd_t *pmd;
-	pte_t *ptep, pte;
-
 	/*
 	 * XXX we might need to change this if we add VIRTUAL_BUG_ON for
 	 * architectures that do not vmalloc module space
 	 */
 	VIRTUAL_BUG_ON(!is_vmalloc_or_module_addr(vmalloc_addr));
-
-	if (pgd_none(*pgd))
-		return NULL;
-	if (WARN_ON_ONCE(pgd_leaf(*pgd)))
-		return NULL; /* XXX: no allowance for huge pgd */
-	if (WARN_ON_ONCE(pgd_bad(*pgd)))
-		return NULL;
-
-	p4d = p4d_offset(pgd, addr);
-	if (p4d_none(*p4d))
-		return NULL;
-	if (p4d_leaf(*p4d))
-		return p4d_page(*p4d) + ((addr & ~P4D_MASK) >> PAGE_SHIFT);
-	if (WARN_ON_ONCE(p4d_bad(*p4d)))
-		return NULL;
-
-	pud = pud_offset(p4d, addr);
-	if (pud_none(*pud))
-		return NULL;
-	if (pud_leaf(*pud))
-		return pud_page(*pud) + ((addr & ~PUD_MASK) >> PAGE_SHIFT);
-	if (WARN_ON_ONCE(pud_bad(*pud)))
-		return NULL;
-
-	pmd = pmd_offset(pud, addr);
-	if (pmd_none(*pmd))
-		return NULL;
-	if (pmd_leaf(*pmd))
-		return pmd_page(*pmd) + ((addr & ~PMD_MASK) >> PAGE_SHIFT);
-	if (WARN_ON_ONCE(pmd_bad(*pmd)))
-		return NULL;
-
-	ptep = pte_offset_kernel(pmd, addr);
-	pte = ptep_get(ptep);
-	if (pte_present(pte))
-		page = pte_page(pte);
-
-	return page;
+	return walk_to_page_node(first_memory_node, vmalloc_addr);
 }
 EXPORT_SYMBOL(vmalloc_to_page);
 
@@ -2490,13 +2492,39 @@ static void free_vmap_area_noflush(struct vmap_area *va)
 		schedule_work(&drain_vmap_work);
 }
 
+#ifdef CONFIG_KERNEL_REPLICATION
+static void vunmap_range_noflush_replicas(unsigned long start, unsigned long end)
+{
+	if (numa_addr_has_replica((void *)start)) {
+		int node;
+		kmsan_vunmap_range_noflush(start, end);
+
+		/**
+		 *  In some scenarios we might clear
+		 *  empty entries here, which is totally fine
+		 */
+		for_each_memory_node(node)
+			__vunmap_range_noflush_pgd(per_node_pgd(&init_mm, node), start, end);
+	} else {
+		vunmap_range_noflush(start, end);
+	}
+}
+#else
+static void vunmap_range_noflush_replicas(unsigned long start, unsigned long end)
+{
+	vunmap_range_noflush(start, end);
+}
+#endif
+
 /*
  * Free and unmap a vmap area
  */
 static void free_unmap_vmap_area(struct vmap_area *va)
 {
 	flush_cache_vunmap(va->va_start, va->va_end);
-	vunmap_range_noflush(va->va_start, va->va_end);
+
+	vunmap_range_noflush_replicas(va->va_start, va->va_end);
+
 	if (debug_pagealloc_enabled_static())
 		flush_tlb_kernel_range(va->va_start, va->va_end);
 
@@ -3193,7 +3221,11 @@ void __init vm_area_register_early(struct vm_struct *vm, size_t align)
 	BUG_ON(vmap_initialized);
 
 	for (p = &vmlist; (cur = *p) != NULL; p = &cur->next) {
-		if ((unsigned long)cur->addr - addr >= vm->size)
+		/* Why change this? vm_structs for kerenl text
+		 * might end up outside of vmalloc region. (kernel replcation + nokaslr case)
+		 * In that case, underflow will happen and normal vm will be added in the beginning
+		 */
+		if ((unsigned long)cur->addr >= addr + vm->size)
 			break;
 		addr = ALIGN((unsigned long)cur->addr + cur->size, align);
 	}
@@ -3357,17 +3389,74 @@ struct vm_struct *remove_vm_area(const void *addr)
 	return vm;
 }
 
+#ifdef CONFIG_KERNEL_REPLICATION
+static inline void set_direct_map_page_replicas(const struct vm_struct *area,
+						struct page *page,
+						int (*set_direct_map)(struct page *page))
+{
+	if (area->replicated) {
+		struct page *cursor;
+
+		list_for_each_entry(cursor, &page->lru, lru) {
+			if (page_address(cursor))
+				set_direct_map(cursor);
+		}
+	}
+}
+#endif /* CONFIG_KERNEL_REPLICATION */
+
 static inline void set_area_direct_map(const struct vm_struct *area,
 				       int (*set_direct_map)(struct page *page))
 {
 	unsigned long i;
 
 	/* HUGE_VMALLOC passes small pages to set_direct_map */
-	for (i = 0; i < area->nr_pages; i++)
+	for (i = 0; i < area->nr_pages; i++) {
 		if (page_address(area->pages[i]))
 			set_direct_map(area->pages[i]);
+#ifdef CONFIG_KERNEL_REPLICATION
+		set_direct_map_page_replicas(area,
+				area->pages[i], set_direct_map);
+#endif /* CONFIG_KERNEL_REPLICATION */
+	}
 }
 
+#ifdef CONFIG_KERNEL_REPLICATION
+static void vm_account_replicated_range(struct vm_struct *area,
+					struct page *page,
+					unsigned long *s,
+					unsigned long *e,
+					int *flush)
+{
+	int flush_dmap = 0;
+	unsigned long start = ULONG_MAX, end = 0;
+	unsigned int page_order = vm_area_page_order(area);
+
+	if (area->replicated) {
+		struct page *cursor;
+
+		list_for_each_entry(cursor, &page->lru, lru) {
+			unsigned long addr = (unsigned long)page_address(cursor);
+
+			if (addr) {
+				unsigned long page_size;
+
+				page_size = PAGE_SIZE << page_order;
+				start = min(addr, start);
+				end = max(addr + page_size, end);
+				flush_dmap = 1;
+			}
+		}
+	}
+
+	if (flush_dmap)
+		*flush = flush_dmap;
+
+	*s = start;
+	*e = end;
+}
+#endif /* CONFIG_KERNEL_REPLICATION */
+
 /*
  * Flush the vm mapping and reset the direct map.
  */
@@ -3393,6 +3482,10 @@ static void vm_reset_perms(struct vm_struct *area)
 			end = max(addr + page_size, end);
 			flush_dmap = 1;
 		}
+#ifdef CONFIG_KERNEL_REPLICATION
+		vm_account_replicated_range(area, area->pages[i],
+				&start, &end, &flush_dmap);
+#endif /* CONFIG_KERNEL_REPLICATION */
 	}
 
 	/*
@@ -3438,6 +3531,30 @@ void vfree_atomic(const void *addr)
 		schedule_work(&p->wq);
 }
 
+#ifdef CONFIG_KERNEL_REPLICATION
+static void vfree_page_replicas(struct vm_struct *area, struct page *page)
+{
+	if (area->replicated) {
+		struct page *cursor, *tmp;
+
+		list_for_each_entry_safe(cursor, tmp, &page->lru, lru) {
+			BUG_ON(!cursor);
+
+			list_del(&cursor->lru);
+			mod_lruvec_page_state(cursor, NR_VMALLOC, -1);
+			/*
+			 * High-order allocs for huge vmallocs are split, so
+			 * can be freed as an array of order-0 allocations
+			 */
+			__free_pages(cursor, 0);
+			cond_resched();
+		}
+	}
+}
+#else
+static void vfree_page_replicas(struct vm_struct *area, struct page *page) { }
+#endif /* CONFIG_KERNEL_REPLICATION */
+
 /*
  * vm_area_free_pages - free a range of pages from a vmalloc allocation
  * @vm: the vm_struct containing the pages
@@ -3455,8 +3572,10 @@ static void vm_area_free_pages(struct vm_struct *vm, unsigned long start_idx,
 	unsigned long i;
 
 	if (!(vm->flags & VM_MAP_PUT_PAGES)) {
-		for (i = start_idx; i < end_idx; i++)
+		for (i = start_idx; i < end_idx; i++) {
+			vfree_page_replicas(vm, vm->pages[i]);
 			mod_lruvec_page_state(vm->pages[i], NR_VMALLOC, -1);
+		}
 	}
 	free_pages_bulk(vm->pages + start_idx, end_idx - start_idx);
 
@@ -3862,6 +3981,58 @@ memalloc_restore_scope(unsigned int flags)
 		memalloc_flags_restore(flags);
 }
 
+static int __vmap_pages_range_retry(unsigned long addr, unsigned long end,
+		pgprot_t prot, struct page **pages, unsigned int page_shift,
+		gfp_t gfp_mask, bool nofail)
+{
+	int ret;
+
+	do {
+		ret = __vmap_pages_range(addr, end, prot, pages,
+					page_shift, gfp_mask);
+		if (nofail && (ret < 0))
+			schedule_timeout_uninterruptible(1);
+	} while (nofail && (ret < 0));
+
+	return ret;
+}
+
+#ifdef CONFIG_KERNEL_REPLICATION
+static int __vmap_pages_range_retry_replica_aware(unsigned long addr, unsigned long end,
+		pgprot_t prot, struct page **pages, unsigned int page_shift,
+		gfp_t gfp_mask, bool nofail, unsigned long flags)
+{
+	int ret;
+
+	if (flags & VM_NUMA_SHARED) {
+		int nid;
+
+		ret = kmsan_vmap_pages_range_noflush(addr, end, prot, pages,
+						page_shift, gfp_mask);
+		for_each_memory_node(nid) {
+			pgd_t *pgd = per_node_pgd(&init_mm, nid);
+
+			do {
+				ret = __vmap_pages_range_noflush_pgd(pgd, addr, end, prot, pages,
+						page_shift);
+				if (nofail && (ret < 0))
+					schedule_timeout_uninterruptible(1);
+			} while (nofail && (ret < 0));
+
+			if (ret < 0)
+				goto out;
+		}
+
+		flush_cache_vmap(addr, end);
+	} else {
+		ret = __vmap_pages_range_retry(addr, end, prot, pages,
+						page_shift, gfp_mask, nofail);
+	}
+out:
+	return ret;
+}
+#endif
+
 static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
 				 pgprot_t prot, unsigned int page_shift,
 				 int node)
@@ -3942,12 +4113,14 @@ static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
 	 * by the scope API
 	 */
 	flags = memalloc_apply_gfp_scope(gfp_mask);
-	do {
-		ret = __vmap_pages_range(addr, addr + size, prot, area->pages,
-				page_shift, nested_gfp);
-		if (nofail && (ret < 0))
-			schedule_timeout_uninterruptible(1);
-	} while (nofail && (ret < 0));
+
+#ifdef CONFIG_KERNEL_REPLICATION
+	ret = __vmap_pages_range_retry_replica_aware(addr, addr + size, prot, area->pages,
+					page_shift, nested_gfp, nofail, area->flags);
+#else
+	ret = __vmap_pages_range_retry(addr, addr + size, prot, area->pages,
+					page_shift, nested_gfp, nofail);
+#endif
 	memalloc_restore_scope(flags);
 
 	if (ret < 0) {
@@ -4076,6 +4249,16 @@ void *__vmalloc_node_range_noprof(unsigned long size, unsigned long align,
 		goto fail;
 	}
 
+#ifdef CONFIG_KERNEL_REPLICATION
+	/* Check, that for requested region correct replication flag was passed */
+	if (WARN_ON_ONCE(numa_addr_has_replica(area->addr) && !(vm_flags & VM_NUMA_SHARED)))
+		vm_flags |= VM_NUMA_SHARED;
+
+	if (WARN_ON_ONCE(!numa_addr_has_replica(area->addr) && (vm_flags & VM_NUMA_SHARED)))
+		vm_flags &= ~VM_NUMA_SHARED;
+
+	area->node = node;
+#endif
 	/*
 	 * Prepare arguments for __vmalloc_area_node() and
 	 * kasan_unpoison_vmalloc().
@@ -4165,6 +4348,129 @@ void *__vmalloc_node_noprof(unsigned long size, unsigned long align,
 	return __vmalloc_node_range_noprof(size, align, VMALLOC_START, VMALLOC_END,
 				gfp_mask, PAGE_KERNEL, 0, node, caller);
 }
+
+#ifdef CONFIG_KERNEL_REPLICATION
+static void numa_replicate_page_range(struct page **src, struct page **dst, int nr_pages)
+{
+	int i;
+	void *from, *to;
+
+	for (i = 0; i < nr_pages; i++) {
+		from = kmap_local_page(src[i]);
+		to = kmap_local_page(dst[i]);
+
+		copy_page(to, from);
+
+		kunmap_local(to);
+		kunmap_local(from);
+	}
+}
+
+int __vmalloc_node_replicate_range(const void *addr, gfp_t gfp_mask,
+		pgprot_t prot, unsigned long vm_flags)
+{
+	int i, ret, node = 0;
+	struct vm_struct *area;
+	unsigned int page_order;
+	unsigned int nr_allocated;
+	struct page **pages;
+	unsigned long area_start, area_end;
+	const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
+	unsigned long array_size;
+	unsigned int flags;
+
+
+	gfp_mask |= __GFP_NOWARN;
+	if (!(gfp_mask & (GFP_DMA | GFP_DMA32)))
+		gfp_mask |= __GFP_HIGHMEM;
+
+	if (unlikely(!numa_addr_has_replica(addr)))
+		return -EINVAL;
+
+	area = find_vm_area(addr);
+	if (unlikely(!area))
+		return -ENOENT;
+
+	if (area->node == NUMA_NO_NODE)
+		return -EINVAL;
+
+	array_size = sizeof(struct page *) * area->nr_pages;
+	if (array_size > PAGE_SIZE)
+		pages = __vmalloc(array_size, nested_gfp);
+	else
+		pages = kmalloc(array_size, nested_gfp);
+
+	if (!pages)
+		return -ENOMEM;
+
+	page_order = vm_area_page_order(area);
+	for (i = 0; i < area->nr_pages; i++)
+		INIT_LIST_HEAD(&area->pages[i]->lru);
+
+	area_start = (unsigned long)area->addr;
+	area_end = (unsigned long)(area->addr + area->nr_pages * PAGE_SIZE);
+
+	for_each_memory_node(node) {
+		if (area->node == node)
+			continue;
+
+		nr_allocated = vm_area_alloc_pages(gfp_mask | __GFP_NOWARN,
+			node, page_order, area->nr_pages, pages);
+		if (nr_allocated != area->nr_pages)
+			goto fail_alloc_pages;
+
+		for (i = 0; i < area->nr_pages; i++)
+			list_add(&pages[i]->lru, &area->pages[i]->lru);
+
+		__vunmap_range_noflush_pgd(init_mm.pgd_numa[node],
+					 area_start, area_end);
+
+		/*
+		 * We can't fail here (hopefully)
+		 * Possible errors: not enough memory for tables and not empty entries.
+		 * Both unrealistic because we just cleared entries in existed tables.
+		 */
+
+		numa_replicate_page_range(area->pages, pages, area->nr_pages);
+
+		flags = memalloc_apply_gfp_scope(gfp_mask);
+
+
+		ret = __vmap_pages_range_noflush_pgd(per_node_pgd(&init_mm, node), area_start,
+					area_end, prot, area->pages, page_order + PAGE_SHIFT);
+
+		memalloc_restore_scope(flags);
+		if (ret != 0)
+			goto fail_map_pages;
+
+		for (i = 0; i < area->nr_pages; i++)
+			pages[i] = NULL;
+	}
+	kvfree(pages);
+	flush_cache_vmap(area_start, area_end);
+	flush_tlb_kernel_range(area_start, area_end);
+	area->replicated = true;
+
+	return 0;
+fail_alloc_pages:
+	for (i = 0; i < nr_allocated; i++)
+		__free_pages(pages[i], 0);
+
+fail_map_pages:
+	kfree(pages);
+	for (i = 0; i < area->nr_pages; i++) {
+		struct page *page, *tmp;
+
+		list_for_each_entry_safe(page, tmp, &area->pages[i]->lru, lru) {
+			list_del(&page->lru);
+			__free_pages(page, 0);
+		}
+	}
+
+	return ret;
+}
+#endif /* CONFIG_KERNEL_REPLICATION */
+
 /*
  * This is only for performance analysis of vmalloc and stress purpose.
  * It is required by vmalloc test module, therefore do not use it other
-- 
2.34.1



  parent reply	other threads:[~2026-08-27 16:25 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-27 16:11 [RFC PATCH 00/18] mm: arm64: Add kernel replication feature Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 01/18] mm: arm64 add Kconfig option for kernel replication Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 02/18] arm64: align kernel text and rodata Nikita Panov
2026-08-27 17:35   ` Lorenzo Stoakes (ARM)
2026-08-28 13:00     ` Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 03/18] mm: allow per-NUMA node local P4D/PUD/PMD/PTE allocation Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 04/18] arm64: add arch callbacks for kernel replication Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 05/18] mm: per-NUMA node replication core infrastructure Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 06/18] mm: add support of memory protection for NUMA replicas Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 07/18] arm64: " Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 08/18] mm: set memory permissions for BPF handlers replicas Nikita Panov
2026-08-27 16:11 ` Nikita Panov [this message]
2026-08-27 16:11 ` [RFC PATCH 10/18] arm64: enable per-NUMA node kernel text and rodata replication Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 11/18] mm: " Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 12/18] arm64: make power management aware about kernel replication Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 13/18] arm64: make kernel text patching aware about replicas Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 14/18] arm64: add correct alignment to kimage in efi code Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 15/18] arm64: add support of NUMA replication for ptdump Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 16/18] arm64: add kernel modules text and rodata replication support Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 17/18] mm: init kernel modules with " Nikita Panov
2026-08-27 16:11 ` [RFC PATCH 18/18] mm: introduce kernel cmdline option "kernel_replication=" Nikita Panov
2026-08-27 17:25 ` [RFC PATCH 00/18] mm: arm64: Add kernel replication feature Lorenzo Stoakes (ARM)
2026-08-27 19:04   ` David Hildenbrand (Arm)
2026-08-28 15:05     ` Artem Kuzin
2026-08-28 13:03   ` Nikita Panov
2026-08-27 19:11 ` Matthew Wilcox
2026-08-28 13:35   ` Nikita Panov
2026-08-28 17:58 ` Christoph Lameter (Ampere)
2026-08-28 20:58 ` Yang Shi

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=20260827161158.3618409-10-panov.nikita@huawei.com \
    --to=panov.nikita@huawei.com \
    --cc=akpm@linux-foundation.org \
    --cc=artem.kuzin@huawei.com \
    --cc=catalin.marinas@arm.com \
    --cc=cl@gentwo.org \
    --cc=david@kernel.org \
    --cc=liam@infradead.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=linux@armlinux.org.uk \
    --cc=ljs@kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=mhocko@suse.com \
    --cc=rppt@kernel.org \
    --cc=surenb@google.com \
    --cc=vbabka@kernel.org \
    --cc=wangkefeng.wang@huawei.com \
    --cc=will@kernel.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