Re: [PATCH 42/46] mm/huge_memory: split huge PUDs when adjusting VMA boundaries

From: David Hildenbrand (Arm)

Date: Thu Oct 29 2026 - 10:32:37 EST


On 10/29/26 12:45, Kiryl Shutsemau wrote:
> 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.

__split_huge_pud() must split a leave. Either by zapping it (what we do right
now) or by populating page tables (as in Usama's PoC).

We can throw in here a

if (WARN_ON_ONCE(!pud_leaf(pud)))
return;

But I don't see how that would be required.

If we run into an un-splittable PUD something else is completely messed up.

(this is not hugetlb code :) )

--
Cheers,

David