diff --git a/mm/memory.c b/mm/memory.c index f69fbc251198..c19cbba60d04 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -4023,6 +4023,75 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) return ret; } +static bool vmf_pte_range_changed(struct vm_fault *vmf, int nr_pages) +{ + int i; + + if (nr_pages == 1) + return vmf_pte_changed(vmf); + + for (i = 0; i < nr_pages; i++) { + if (!pte_none(ptep_get_lockless(vmf->pte + i))) + return true; + } + + return false; +} + +#ifdef CONFIG_FLEXIBLE_THP +static struct folio *alloc_anon_folio(struct vm_fault *vmf) +{ + int i; + unsigned long addr; + struct vm_area_struct *vma = vmf->vma; + int preferred = arch_wants_pte_order(vma) ? : PAGE_ALLOC_COSTLY_ORDER; + int orders[] = { + preferred, + preferred > PAGE_ALLOC_COSTLY_ORDER ? PAGE_ALLOC_COSTLY_ORDER : 0, + 0, + }; + + if (vmf_orig_pte_uffd_wp(vmf)) + goto fallback; + + for (i = 0; orders[i]; i++) { + addr = ALIGN_DOWN(vmf->address, PAGE_SIZE << orders[i]); + if (addr >= vma->vm_start && addr + (PAGE_SIZE << orders[i]) <= vma->vm_end) + break; + } + + if (!orders[i]) + goto fallback; + + vmf->pte = pte_offset_map(vmf->pmd, addr); + + for (; orders[i]; i++) { + if (!vmf_pte_range_changed(vmf, 1 << orders[i])) + break; + } + + pte_unmap(vmf->pte); + vmf->pte = NULL; + + for (; orders[i]; i++) { + struct folio *folio + gfp_t gfp = vma_thp_gfp_mask(vma); + + addr = ALIGN_DOWN(vmf->address, PAGE_SIZE << orders[i]); + folio = vma_alloc_folio(gfp, orders[i], vma, addr, true); + if (folio) { + clear_huge_page(&folio->page, addr, 1 << orders[i]); + vmf->address = addr; + return folio; + } + } +fallback: + return vma_alloc_zeroed_movable_folio(vma, vmf->address); +} +#else +#define alloc_anon_folio(vmf) vma_alloc_zeroed_movable_folio((vmf)->vma, (vmf)->address) +#endif + /* * We enter with non-exclusive mmap_lock (to exclude vma changes, * but allow concurrent faults), and pte mapped but not yet locked.