[RFC PATCH 07/18] arm64: add support of memory protection for NUMA replicas

From: Nikita Panov

Date: Thu Aug 27 2026 - 12:25:22 EST


Acked-by: Artem Kuzin <artem.kuzin@xxxxxxxxxx>
Acked-by: Alexander Grubnikov <alexander.grubnikov@xxxxxxxxxx>
Acked-by: Ilya Hanov <ilya.hanov@xxxxxxxxxxxxxxxxxxx>
Acked-by: Denis Darvish <darvish.denis@xxxxxxxxxx>
Signed-off-by: Nikita Panov <panov.nikita@xxxxxxxxxx>
---
arch/arm64/mm/pageattr.c | 72 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 70 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index bbe98ac9ad8c..50a76385b184 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -9,6 +9,7 @@
#include <linux/sched.h>
#include <linux/vmalloc.h>
#include <linux/pagewalk.h>
+#include <linux/numa_kernel_replication.h>

#include <asm/cacheflush.h>
#include <asm/pgtable-prot.h>
@@ -102,7 +103,7 @@ bool can_set_direct_map(void)
arm64_kfence_can_set_direct_map() || is_realm_world();
}

-static int update_range_prot(unsigned long start, unsigned long size,
+static int update_range_prot_pgd(pgd_t *pgtable, unsigned long start, unsigned long size,
pgprot_t set_mask, pgprot_t clear_mask)
{
struct page_change_data data;
@@ -123,12 +124,19 @@ static int update_range_prot(unsigned long start, unsigned long size,
* must be eliminated by splitting the mapping.
*/
ret = walk_kernel_page_table_range_lockless(start, start + size,
- &pageattr_ops, NULL, &data);
+ &pageattr_ops, pgtable, &data);
lazy_mmu_mode_disable();

return ret;
}

+static int update_range_prot(unsigned long start, unsigned long size,
+ pgprot_t set_mask, pgprot_t clear_mask)
+{
+ return update_range_prot_pgd(NULL, start, size, set_mask, clear_mask);
+}
+
+
static int __change_memory_common(unsigned long start, unsigned long size,
pgprot_t set_mask, pgprot_t clear_mask)
{
@@ -147,6 +155,22 @@ static int __change_memory_common(unsigned long start, unsigned long size,
return ret;
}

+#ifdef CONFIG_KERNEL_REPLICATION
+static int __change_memory_common_replicas(unsigned long start, unsigned long size,
+ pgprot_t set_mask, pgprot_t clear_mask)
+{
+ int nid;
+ int ret;
+
+ for_each_memory_node(nid) {
+ ret |= update_range_prot_pgd(per_node_pgd(&init_mm, nid), start, size, set_mask, clear_mask);
+ }
+
+ flush_tlb_kernel_range(start, start + size);
+ return ret;
+}
+#endif /* CONFIG_KERNEL_REPLICATION */
+
static int change_memory_common(unsigned long addr, int numpages,
pgprot_t set_mask, pgprot_t clear_mask)
{
@@ -211,6 +235,20 @@ static int change_memory_common(unsigned long addr, int numpages,
return __change_memory_common(start, size, set_mask, clear_mask);
}

+#ifdef CONFIG_KERNEL_REPLICATION
+static int numa_change_memory_common(unsigned long addr, int numpages,
+ pgprot_t set_mask, pgprot_t clear_mask)
+{
+ int ret;
+
+ ret = change_memory_common(addr, numpages, set_mask, clear_mask);
+ if (ret)
+ return ret;
+
+ return __change_memory_common_replicas(addr, numpages * PAGE_SIZE, set_mask, clear_mask);
+}
+#endif /* CONFIG_KERNEL_REPLICATION */
+
int set_memory_ro(unsigned long addr, int numpages)
{
return change_memory_common(addr, numpages,
@@ -239,6 +277,36 @@ int set_memory_x(unsigned long addr, int numpages)
__pgprot(PTE_PXN));
}

+#ifdef CONFIG_KERNEL_REPLICATION
+int numa_set_memory_x(unsigned long addr, int numpages)
+{
+ return numa_change_memory_common(addr, numpages,
+ __pgprot(PTE_MAYBE_GP),
+ __pgprot(PTE_PXN));
+}
+
+int numa_set_memory_nx(unsigned long addr, int numpages)
+{
+ return numa_change_memory_common(addr, numpages,
+ __pgprot(PTE_PXN),
+ __pgprot(PTE_MAYBE_GP));
+}
+
+int numa_set_memory_ro(unsigned long addr, int numpages)
+{
+ return numa_change_memory_common(addr, numpages,
+ __pgprot(PTE_RDONLY),
+ __pgprot(PTE_WRITE));
+}
+
+int numa_set_memory_rw(unsigned long addr, int numpages)
+{
+ return numa_change_memory_common(addr, numpages,
+ __pgprot(PTE_WRITE),
+ __pgprot(PTE_RDONLY));
+}
+#endif /*CONFIG_KERNEL_REPLICATION*/
+
int set_memory_valid(unsigned long addr, int numpages, int enable)
{
if (enable)
--
2.34.1