[PATCH 04/18] hugetlb_vmemmap: Use try_update_vmemmap_pte to update in-use PTEs
From: James Houghton <hidden>
Date: 2026-07-08 03:11:58
Also in:
linux-mm, lkml
Subsystem:
hugetlb subsystem, loongarch, memory management, memory management - core, risc-v architecture, the rest, x86 architecture (32-bit and 64-bit) · Maintainers:
Muchun Song, Oscar Salvador, Huacai Chen, Andrew Morton, David Hildenbrand, Paul Walmsley, Palmer Dabbelt, Albert Ou, Linus Torvalds, Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen
set_pte_at() cannot be used to replace in-use vmemmap PTEs on arm64, so replace it with a more specific routine, try_update_vmemmap_pte(). try_update_vmemmap_pte() is used for modifying page table entries such that there is a guarantee that no fault will be taken. For the x86, riscv, and loongarch implementations, please note one difference: set_pte() does not invoke page_table_check_ptes_set(), but set_pte_at(), the call we are about to replace, does. However, this is a functional no-op because page_table_check_ptes_set() does nothing for init_mm PTEs, which vmemmap PTEs are. Signed-off-by: James Houghton <redacted> --- arch/loongarch/include/asm/pgtable.h | 8 ++++ arch/riscv/include/asm/pgtable.h | 8 ++++ arch/x86/include/asm/pgtable.h | 8 ++++ include/linux/pgtable.h | 21 +++++++++++ mm/hugetlb_vmemmap.c | 56 +++++++++++++++++++--------- 5 files changed, 84 insertions(+), 17 deletions(-)
diff --git a/arch/loongarch/include/asm/pgtable.h b/arch/loongarch/include/asm/pgtable.h
index 223528c04d73..e7b65056ef73 100644
--- a/arch/loongarch/include/asm/pgtable.h
+++ b/arch/loongarch/include/asm/pgtable.h@@ -638,6 +638,14 @@ static inline long pmd_protnone(pmd_t pmd) #define pmd_leaf(pmd) ((pmd_val(pmd) & _PAGE_HUGE) != 0) #define pud_leaf(pud) ((pud_val(pud) & _PAGE_HUGE) != 0) +#define __HAVE_ARCH_TRY_UPDATE_VMEMMAP_PTE +static inline int try_update_vmemmap_pte(unsigned long addr, pte_t *ptep, + pte_t pte) +{ + set_pte(ptep, pte); + return 0; +} + /* * We provide our own get_unmapped area to cope with the virtual aliasing * constraints placed on us by the cache architecture.
diff --git a/arch/riscv/include/asm/pgtable.h b/arch/riscv/include/asm/pgtable.h
index 5d5756bda82e..a11c569ee2f0 100644
--- a/arch/riscv/include/asm/pgtable.h
+++ b/arch/riscv/include/asm/pgtable.h@@ -1162,6 +1162,14 @@ static inline pud_t pud_modify(pud_t pud, pgprot_t newprot) #endif /* CONFIG_TRANSPARENT_HUGEPAGE */ +#define __HAVE_ARCH_TRY_UPDATE_VMEMMAP_PTE +static inline int try_update_vmemmap_pte(unsigned long addr, pte_t *ptep, + pte_t pte) +{ + set_pte(ptep, pte); + return 0; +} + /* * Encode/decode swap entries and swap PTEs. Swap PTEs are all PTEs that * are !pte_none() && !pte_present().
diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h
index ac295ca6c92f..974151564071 100644
--- a/arch/x86/include/asm/pgtable.h
+++ b/arch/x86/include/asm/pgtable.h@@ -1372,6 +1372,14 @@ static inline pmd_t pmdp_establish(struct vm_area_struct *vma, } #endif +#define __HAVE_ARCH_TRY_UPDATE_VMEMMAP_PTE +static inline int try_update_vmemmap_pte(unsigned long addr, pte_t *ptep, + pte_t pte) +{ + set_pte(ptep, pte); + return 0; +} + #ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD static inline pud_t pudp_establish(struct vm_area_struct *vma, unsigned long address, pud_t *pudp, pud_t pud)
diff --git a/include/linux/pgtable.h b/include/linux/pgtable.h
index 2981e386da7b..e1abccf76bb4 100644
--- a/include/linux/pgtable.h
+++ b/include/linux/pgtable.h@@ -457,6 +457,27 @@ static inline void set_ptes(struct mm_struct *mm, unsigned long addr, #endif #define set_pte_at(mm, addr, ptep, pte) set_ptes(mm, addr, ptep, pte, 1) +#ifndef __HAVE_ARCH_TRY_UPDATE_VMEMMAP_PTE +/* + * try_update_vmemmap_pte - Remap PTEs used by the vmemmap. + * @addr: Base address of the remapped PTE. + * @ptep: Page table pointer to be overwritten. + * @pte: Page table entry to write. + * + * This function is only to be used to update PTEs that map the vmemmap. The + * only valid transitions supported by this function are: leaf-level + * (PAGE_SIZE), valid-to-valid. The pfn and prot bits may be changed. + * + * Implementations of this function must ensure that, while the update is taking + * place, CPUs will not fault on the remapped virtual address. + */ +static inline int try_update_vmemmap_pte(unsigned long addr, pte_t *ptep, + pte_t pte) +{ + return -EOPNOTSUPP; +} +#endif + #ifndef __HAVE_ARCH_PTEP_SET_ACCESS_FLAGS extern int ptep_set_access_flags(struct vm_area_struct *vma, unsigned long address, pte_t *ptep,
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 5fee01bf5b34..3951c043164a 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c@@ -34,7 +34,7 @@ * operations. */ struct vmemmap_remap_walk { - void (*remap_pte)(pte_t *pte, unsigned long addr, + int (*remap_pte)(pte_t *pte, unsigned long addr, struct vmemmap_remap_walk *walk); unsigned long nr_walked;
@@ -141,11 +141,13 @@ static int vmemmap_pte_entry(pte_t *pte, unsigned long addr, unsigned long next, struct mm_walk *walk) { struct vmemmap_remap_walk *vmemmap_walk = walk->private; + int ret = 0; - vmemmap_walk->remap_pte(pte, addr, vmemmap_walk); - vmemmap_walk->nr_walked++; + ret = vmemmap_walk->remap_pte(pte, addr, vmemmap_walk); + if (!ret) + vmemmap_walk->nr_walked++; - return 0; + return ret; } static const struct mm_walk_ops vmemmap_remap_ops = {
@@ -197,18 +199,20 @@ static void free_vmemmap_page_list(struct list_head *list) free_vmemmap_page(page); } -static void vmemmap_remap_pte(pte_t *pte, unsigned long addr, - struct vmemmap_remap_walk *walk) +static int vmemmap_remap_pte(pte_t *pte, unsigned long addr, + struct vmemmap_remap_walk *walk) { struct page *page = pte_page(ptep_get(pte)); pte_t entry; + bool head; + int ret; + + head = walk->nr_walked == 0 && walk->vmemmap_head; /* Remapping the head page requires r/w */ - if (unlikely(walk->nr_walked == 0 && walk->vmemmap_head)) { + if (unlikely(head)) { VM_WARN_ON_ONCE(!PageHead((const struct page *)addr)); - list_del(&walk->vmemmap_head->lru); - /* * Makes sure that preceding stores to the page contents from * vmemmap_remap_free() become visible before the set_pte_at()
@@ -227,17 +231,30 @@ static void vmemmap_remap_pte(pte_t *pte, unsigned long addr, entry = mk_pte(walk->vmemmap_tail, PAGE_KERNEL_RO); } + ret = try_update_vmemmap_pte(addr, pte, entry); + if (ret) + return ret; + + /* We successfully overwrote the vmemmap PTE, so we can free + * the vmemmap page that was just unmapped, and if we mapped + * the new head page, remove it from the list so that it + * doesn't get freed later. + */ list_add(&page->lru, walk->vmemmap_pages); - set_pte_at(&init_mm, addr, pte, entry); + if (head) + list_del(&walk->vmemmap_head->lru); + + return 0; } -static void vmemmap_restore_pte(pte_t *pte, unsigned long addr, - struct vmemmap_remap_walk *walk) +static int vmemmap_restore_pte(pte_t *pte, unsigned long addr, + struct vmemmap_remap_walk *walk) { struct page *src = pte_page(ptep_get(pte)), *dst; + int ret; if (WARN_ON_ONCE(!walk->vmemmap_tail)) - return; + return -EINVAL; /* * When restoring a partially-HVOed page, keep the copied head page
@@ -245,20 +262,25 @@ static void vmemmap_restore_pte(pte_t *pte, unsigned long addr, * page. */ if (walk->vmemmap_tail != src) - return; + return 0; VM_WARN_ON_ONCE(PageHead((const struct page *)addr)); dst = list_first_entry(walk->vmemmap_pages, struct page, lru); - list_del(&dst->lru); copy_page(page_to_virt(dst), page_to_virt(src)); /* * Makes sure that preceding stores to the page contents become visible - * before the set_pte_at() write. + * before the try_update_vmemmap_pte() write. */ smp_wmb(); - set_pte_at(&init_mm, addr, pte, mk_pte(dst, PAGE_KERNEL)); + + ret = try_update_vmemmap_pte(addr, pte, mk_pte(dst, PAGE_KERNEL)); + if (ret) + return ret; + + list_del(&dst->lru); + return 0; } /**
--
2.55.0.795.g602f6c329a-goog