Re: [PATCH v3 3/9] mm/huge_memory: honor MM_CP_TRY_CHANGE_WRITABLE in change_huge_pud()

From: Lorenzo Stoakes (ARM)

Date: Fri Nov 13 2026 - 15:49:04 EST


On Thu, Nov 12, 2026 at 03:20:32PM +0100, Carlos López wrote:
> change_protection() applies vma->vm_page_prot, which lacks the write bit
> for CoW and write-notified mappings. MM_CP_TRY_CHANGE_WRITABLE makes
> entries writable again if a write fault would only set the write bit.
>
> However, change_huge_pud() ignores the flag. For example, device DAX
> mappings are write-notified once soft-dirty tracking is armed through
> /proc/pid/clear_refs, and a protection change then leaves dirty PUDs
> read-only, so the next write to each of them faults.
>
> Fix this by adding can_change_pud_writable(), based on
> can_change_pmd_writable(). It omits the NUMA and uffd-wp checks, as
> change_huge_pud() bails out on those changes.
>
> Signed-off-by: Carlos López <clopez@xxxxxxx>

Is this a real problem that actual people have had?

The why of all this is still eluding me, especially for THP PUD...

> ---
> include/linux/pgtable.h | 5 +++++
> mm/huge_memory.c | 28 +++++++++++++++++++++++++++-
> 2 files changed, 32 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/pgtable.h b/include/linux/pgtable.h
> index 780fe849ff8b..943d7e95f426 100644
> --- a/include/linux/pgtable.h
> +++ b/include/linux/pgtable.h
> @@ -1866,6 +1866,11 @@ static inline int pmd_soft_dirty(pmd_t pmd)
> return 0;
> }
>
> +static inline int pud_soft_dirty(pud_t pud)
> +{
> + return 0;
> +}
> +
> static inline pte_t pte_mksoft_dirty(pte_t pte)
> {
> return pte;
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index eb913acb03b7..080b718eb7de 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -2833,6 +2833,29 @@ int change_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
> return ret;
> }
>
> +#ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD
> +static inline bool can_change_pud_writable(struct vm_area_struct *vma,
> + unsigned long addr, pud_t pud)
> +{
> + struct page *page;
> +
> + if (WARN_ON_ONCE(!(vma->vm_flags & VM_WRITE)))
> + return false;
> +
> + /* Do we need write faults for softdirty tracking? */
> + if (vma_soft_dirty_enabled(vma) && !pud_soft_dirty(pud))
> + return false;
> +
> + if (!(vma->vm_flags & VM_SHARED)) {

This is getting annoying now, you're seemingly _knowingly_ writing deprecated
VMA flag API code on purpose.

> + /* See can_change_pte_writable(). */
> + page = vm_normal_page_pud(vma, addr, pud);
> + return page && PageAnon(page) && PageAnonExclusive(page);
> + }
> +
> + /* See can_change_pte_writable(). */
> + return pud_dirty(pud);
> +}
> +
> /*
> * Returns:
> *
> @@ -2840,7 +2863,6 @@ int change_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
> * - 1: if pud can be skipped
> * - HPAGE_PUD_NR: if pud was successfully processed
> */
> -#ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD
> int change_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,
> pud_t *pudp, unsigned long addr, pgprot_t newprot,
> unsigned long cp_flags)
> @@ -2872,6 +2894,10 @@ int change_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,
> */
> oldpud = pudp_invalidate(vma, addr, pudp);
> entry = pud_modify(oldpud, newprot);
> + /* See change_pte_range(). */
> + if ((cp_flags & MM_CP_TRY_CHANGE_WRITABLE) && !pud_write(entry) &&
> + can_change_pud_writable(vma, addr, entry))
> + entry = pud_mkwrite(entry);
> set_pud_at(mm, addr, pudp, entry);
> tlb_flush_pud_range(tlb, addr, HPAGE_PUD_SIZE);
>
> --
> 2.51.0
>

--
Cheers, Lorenzo