Re: [PATCH 42/46] mm/huge_memory: split huge PUDs when adjusting VMA boundaries
From: Kiryl Shutsemau
Date: Thu Oct 29 2026 - 07:46:18 EST
On Tue, Oct 27, 2026 at 12:18:02AM +0100, David Hildenbrand (Arm) wrote:
> PFN mappings such as VFIO PCI BAR mappings can use PUD leaves while
> allowing VMA splits at page granularity. However, vma_adjust_trans_huge()
> only handles PMDs.
>
> After faulting a writable PUD-sized VFIO BAR mapping, changing its first
> page to read-only with mprotect() makes the PMD walk treat the PUD leaf
> as a PMD table. Applying MADV_DONTFORK to the first PMD-sized chunk leaves
> the PUD spanning two VMAs: the PMD-aligned boundary skips splitting.
>
> Walk the page tables once at each adjusted boundary and split a huge
> PUD or PMD leaf as needed. Handle PUD leaves even when the boundary is
> PMD-aligned.
>
> Fixes: f9e54c3a2f5b7 ("vfio/pci: implement huge_fault support")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: David Hildenbrand (Arm) <david@xxxxxxxxxx>
> ---
> mm/huge_memory.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++--------
> 1 file changed, 60 insertions(+), 10 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 08e58cda7d37d..d090642ac8c2b 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -3503,16 +3503,66 @@ void split_huge_pmd_address(struct vm_area_struct *vma, unsigned long address,
> __split_huge_pmd(vma, pmd, address, freeze);
> }
>
> -static inline void split_huge_pmd_if_needed(struct vm_area_struct *vma, unsigned long address)
> +static bool pud_split_required(const struct vm_area_struct *vma,
> + unsigned long address)
> {
> + if (!IS_ENABLED(CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD))
> + return false;
> + if (IS_ALIGNED(address, HPAGE_PUD_SIZE))
> + return false;
> /*
> - * If the new address isn't hpage aligned and it could previously
> - * contain an hugepage: check if we need to split an huge pmd.
> + * Only selected VMAs support mapping PUDs for now. Lift the
> + * restriction once this changes.
> */
> - if (!IS_ALIGNED(address, HPAGE_PMD_SIZE) &&
> - range_in_vma(vma, ALIGN_DOWN(address, HPAGE_PMD_SIZE),
> - ALIGN(address, HPAGE_PMD_SIZE)))
> - split_huge_pmd_address(vma, address, false);
> + if (!vma_is_dax(vma) && !vma_test_any(vma, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT))
> + return false;
> + return range_in_vma(vma, ALIGN_DOWN(address, HPAGE_PUD_SIZE),
> + ALIGN(address, HPAGE_PUD_SIZE));
> +}
> +
> +static bool pmd_split_required(const struct vm_area_struct *vma,
> + unsigned long address)
> +{
> + if (IS_ALIGNED(address, HPAGE_PMD_SIZE))
> + return false;
> +
> + return range_in_vma(vma, ALIGN_DOWN(address, HPAGE_PMD_SIZE),
> + ALIGN(address, HPAGE_PMD_SIZE));
> +}
> +
> +static void split_huge_leaf(struct vm_area_struct *vma,
> + unsigned long address)
> +{
> + const bool split_pud = pud_split_required(vma, address);
> + const bool split_pmd = pmd_split_required(vma, address);
> + pgd_t *pgd;
> + p4d_t *p4d;
> + pud_t *pud;
> + pmd_t *pmd;
> +
> + /*
> + * mmap, VMA and rmap write locks protect the walk; the split helpers
> + * take the page table locks.
> + */
> + if (!split_pud && !split_pmd)
> + return;
> +
> + pgd = pgd_offset(vma->vm_mm, address);
> + if (!pgd_present(*pgd))
> + return;
> +
> + p4d = p4d_offset(pgd, address);
> + if (!p4d_present(*p4d))
> + return;
> +
> + pud = pud_offset(p4d, address);
> + if (split_pud && pud_leaf(*pud))
> + __split_huge_pud(vma, pud, address);
> + if (!split_pmd || !pud_present(*pud))
> + return;
This looks wrong.
We should bail out here if PUD is was a leaf and wasn't split.
pmd_offset() below expects page table PUD entry.
> + pmd = pmd_offset(pud, address);
> + __split_huge_pmd(vma, pmd, address, false);
> }
>
> void vma_adjust_trans_huge(struct vm_area_struct *vma,
--
Kiryl Shutsemau / Kirill A. Shutemov