[PATCH v2 03/13] arm64/mm: add set_direct_map_default_nosplit()
From: Jan Sebastian Götte
Date: Tue Aug 11 2026 - 13:59:09 EST
set_direct_map_default_noflush() goes through split_kernel_leaf_mapping(),
which takes pgtable_split_lock and may sleep. That makes it unusable from
atomic contexts such as the panic path.
Add set_direct_map_default_nosplit(), which skips the split and only walks
the page tables to apply the permission change. Callers must guarantee the
page is already mapped at page granularity. This holds for any page that
had set_direct_map_invalid_noflush() applied to it, as that call performed
the split.
Architectures that do not need to split the linear map get a generic
fallback to set_direct_map_default_noflush().
Signed-off-by: Jan Sebastian Götte <linux@xxxxxxxx>
---
arch/arm64/include/asm/set_memory.h | 2 ++
arch/arm64/mm/pageattr.c | 21 +++++++++++++++++++++
include/linux/set_memory.h | 17 ++++++++++++++++-
3 files changed, 39 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h
index 90f61b17275e..cb5f44bb5a94 100644
--- a/arch/arm64/include/asm/set_memory.h
+++ b/arch/arm64/include/asm/set_memory.h
@@ -13,6 +13,8 @@ int set_memory_valid(unsigned long addr, int numpages, int enable);
int set_direct_map_invalid_noflush(struct page *page);
int set_direct_map_default_noflush(struct page *page);
+int set_direct_map_default_nosplit(struct page *page);
+#define set_direct_map_default_nosplit set_direct_map_default_nosplit
int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid);
bool kernel_page_present(struct page *page);
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index bbe98ac9ad8c..a7bb6bfa12ed 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -275,6 +275,27 @@ int set_direct_map_default_noflush(struct page *page)
PAGE_SIZE, set_mask, clear_mask);
}
+/* Caller must guarantee @page is already mapped at page granularity. */
+int set_direct_map_default_nosplit(struct page *page)
+{
+ struct page_change_data data = {
+ .set_mask = __pgprot(PTE_PRESENT_VALID_KERNEL | PTE_WRITE),
+ .clear_mask = __pgprot(PTE_PRESENT_INVALID | PTE_RDONLY),
+ };
+ unsigned long addr = (unsigned long)page_address(page);
+ int ret;
+
+ if (!can_set_direct_map())
+ return 0;
+
+ lazy_mmu_mode_enable();
+ ret = walk_kernel_page_table_range_lockless(addr, addr + PAGE_SIZE,
+ &pageattr_ops, NULL, &data);
+ lazy_mmu_mode_disable();
+
+ return ret;
+}
+
static int __set_memory_enc_dec(unsigned long addr,
int numpages,
bool encrypt)
diff --git a/include/linux/set_memory.h b/include/linux/set_memory.h
index 3030d9245f5a..65d0fa010368 100644
--- a/include/linux/set_memory.h
+++ b/include/linux/set_memory.h
@@ -44,7 +44,22 @@ static inline bool kernel_page_present(struct page *page)
{
return true;
}
-#else /* CONFIG_ARCH_HAS_SET_DIRECT_MAP */
+#endif /* !CONFIG_ARCH_HAS_SET_DIRECT_MAP */
+
+/*
+ * Variant of set_direct_map_default_noflush() that is safe to call from atomic
+ * context, for pages the caller knows are already mapped at page granularity.
+ * Architectures that need to split the linear map, and can therefore sleep,
+ * override this.
+ */
+#ifndef set_direct_map_default_nosplit
+static inline int set_direct_map_default_nosplit(struct page *page)
+{
+ return set_direct_map_default_noflush(page);
+}
+#endif
+
+#ifdef CONFIG_ARCH_HAS_SET_DIRECT_MAP
/*
* Some architectures, e.g. ARM64 can disable direct map modifications at
* boot time. Let them overrive this query.
--
2.53.0