Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Jinjiang Tu" <tujinjiang@huawei.com>,
	akpm@linux-foundation.org, david@kernel.org, ljs@kernel.org,
	catalin.marinas@arm.com, will@kernel.org, mark.rutland@arm.com,
	ryan.roberts@arm.com, peterx@redhat.com, dave.jiang@intel.com,
	linux-mm@kvack.org
Cc: wangkefeng.wang@huawei.com, sunnanyong@huawei.com
Subject: Re: [PATCH RESEND 3/3] arm64: Add PUD THP support for DAX and pfnmap
Date: Thu, 29 Oct 2026 12:14:40 +0000	[thread overview]
Message-ID: <sashiko-outbox-189265@kernel.org> (raw)
In-Reply-To: <20261028121404.853944-4-tujinjiang@huawei.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] arm64: mm: local-only TLB flush in pudp_set_access_flags() causes fault livelock
- [High] arm64: mm: missing pudp_set_wrprotect() loses HAFDBS dirty bits and triggers warning
- [High] arm64: mm: missing pudp_huge_get_and_clear() loses concurrent HAFDBS A/D updates
- [Low] arm64: mm: redefinition of pud_trans_huge() when CONFIG_TRANSPARENT_HUGEPAGE=n
--

commit 7dc812ccee714c3818da8b998f785b9073deeacd
Author: Jinjiang Tu <tujinjiang@huawei.com>

arm64: Add PUD THP support for DAX and pfnmap

This patch adds PUD transparent hugepage page table helpers on arm64 and
enables HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD for 4KB page size configurations to
support PUD THP for DAX and pfnmap mappings.

> diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
> index 7de2487e4d353..feca481b20795 100644
> --- a/arch/arm64/include/asm/pgtable.h
> +++ b/arch/arm64/include/asm/pgtable.h

[ ... ]

> @@ -826,6 +838,13 @@ static inline bool pud_leaf(pud_t pud)
>  #define pud_user(pud)		pte_user(pud_pte(pud))
>  #define pud_user_exec(pud)	pte_user_exec(pud_pte(pud))
>  
> +#ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD
> +static inline int pud_trans_huge(pud_t pud)
> +{
> +	return pud_present(pud) && !pud_table(__pud(pud_val(pud) | PTE_VALID));
> +}
> +#endif

[Severity: Low]
Will this cause a redefinition error for pud_trans_huge() when building with
CONFIG_PAGE_SIZE_4KB=y and CONFIG_TRANSPARENT_HUGEPAGE=n (for example,
allnoconfig or tinyconfig)?

In arch/arm64/Kconfig, config ARM64 selects
HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD whenever PAGE_SIZE_4KB is enabled, even
when CONFIG_TRANSPARENT_HUGEPAGE is disabled:

	select HAVE_ARCH_TRANSPARENT_HUGEPAGE
	select HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD if PAGE_SIZE_4KB

However, include/linux/pgtable.h includes asm/pgtable.h and then provides a
fallback definition of pud_trans_huge() whenever CONFIG_TRANSPARENT_HUGEPAGE
is not defined:

#if !defined(CONFIG_TRANSPARENT_HUGEPAGE) || \
	!defined(CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD)
static inline int pud_trans_huge(pud_t pud)
{
	return 0;
}
#endif

Should pud_trans_huge() in arch/arm64/include/asm/pgtable.h also be guarded by
CONFIG_TRANSPARENT_HUGEPAGE (matching pmd_trans_huge() in the same file)?

[ ... ]

> @@ -1207,6 +1231,16 @@ static inline int pmdp_set_access_flags(struct vm_area_struct *vma,
>  }
>  #endif
>  
> +#ifdef CONFIG_ARCH_SUPPORTS_PUD_PFNMAP
> +static inline int pudp_set_access_flags(struct vm_area_struct *vma,
> +					unsigned long address, pud_t *pudp,
> +					pud_t entry, int dirty)
> +{
> +	return __ptep_set_access_flags_anysz(vma, address, (pte_t *)pudp,
> +					     pud_pte(entry), dirty, PUD_SIZE);
> +}
> +#endif

[Severity: High]
Can delegating pudp_set_access_flags() to __ptep_set_access_flags_anysz() lead
to an infinite spurious write page-fault livelock on remote CPUs?

In arch/arm64/mm/fault.c, __ptep_set_access_flags_anysz() only invalidates the
local CPU's TLB via TLBF_NOBROADCAST when upgrading a read-only entry to
writable/dirty, relying on remote CPUs to invalidate their stale read-only TLB
entries on a subsequent permission fault:

	if (pte_same(pte, entry))
		return 0;
	...
	/*
	 * Invalidate the local stale read-only entry.  Remote stale entries
	 * may still cause page faults and be invalidated via
	 * flush_tlb_fix_spurious_fault().
	 */
	if (dirty) {
		...
		__flush_tlb_range(vma, address, address + pgsize, pgsize, level,
				  TLBF_NOWALKCACHE | TLBF_NOBROADCAST);
	}

Suppose two CPUs fault in a shared PUD mapping as hardware read-only
(PTE_RDONLY=1, such as on an initial read fault for fsdax, a PFNMAP/devdax
mapping on a CPU without hardware DBM, or after copy_huge_pud() write-protects
the PUD), and CPU A then writes to the mapping. CPU A updates *pudp via
wp_huge_pud() -> insert_pud() -> pudp_set_access_flags() or
huge_pud_set_accessed() -> touch_pud() -> pudp_set_access_flags(), flushing
only CPU A's local TLB.

When CPU B subsequently writes to the same PUD range, it takes a permission
fault from its stale read-only local TLB entry and enters:

__handle_mm_fault()
  huge_pud_set_accessed()
    touch_pud()
      pudp_set_access_flags()
        __ptep_set_access_flags_anysz()

Because *pudp in memory already matches entry (with PTE_AF, PTE_DIRTY, and
PTE_WRITE set and PTE_RDONLY cleared), pte_same(pte, entry) is true and
__ptep_set_access_flags_anysz() returns 0 without flushing CPU B's TLB.

Unlike the PTE and PMD paths, which call flush_tlb_fix_spurious_fault() and
flush_tlb_fix_spurious_fault_pmd() when ptep_set_access_flags() or
pmdp_set_access_flags() returns 0, neither touch_pud(),
huge_pud_set_accessed(), __handle_mm_fault(), nor insert_pud() performs a
local TLB flush on a spurious PUD fault. Wouldn't CPU B return to userspace
with the stale read-only PUD TLB entry still in place and fault in an infinite
loop?

[ ... ]

> @@ -1281,6 +1315,14 @@ static inline bool pmdp_test_and_clear_young(struct vm_area_struct *vma,
>  }
>  #endif /* CONFIG_TRANSPARENT_HUGEPAGE || CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG */
>  
> +#ifdef CONFIG_ARCH_SUPPORTS_PUD_PFNMAP
> +static inline bool pudp_test_and_clear_young(struct vm_area_struct *vma,
> +		unsigned long address, pud_t *pudp)
> +{
> +	return __ptep_test_and_clear_young(vma, address, (pte_t *)pudp);
> +}
> +#endif
> +
>  static inline pte_t __ptep_get_and_clear_anysz(struct mm_struct *mm,
>  					       unsigned long address,
>  					       pte_t *ptep,

[Severity: High]
Should arm64 also define __HAVE_ARCH_PUDP_HUGE_GET_AND_CLEAR and implement
pudp_huge_get_and_clear() via __ptep_get_and_clear_anysz(..., PUD_SIZE)?

__ptep_get_and_clear_anysz() in arch/arm64/include/asm/pgtable.h already uses
an atomic xchg_relaxed() and has a PUD_SIZE case for
page_table_check_pud_clear(). Without an arch definition of
pudp_huge_get_and_clear(), arm64 falls back to the generic implementation in
include/linux/pgtable.h:

#ifndef __HAVE_ARCH_PUDP_HUGE_GET_AND_CLEAR
static inline pud_t pudp_huge_get_and_clear(struct mm_struct *mm,
					    unsigned long address,
					    pud_t *pudp)
{
	pud_t pud = *pudp;

	pud_clear(pudp);
	page_table_check_pud_clear(mm, address, pud);

	return pud;
}
#endif

When splitting a DAX PUD via split_huge_pud() -> __split_huge_pud_locked() ->
pudp_huge_clear_flush() -> pudp_huge_get_and_clear(), __split_huge_pud_locked()
propagates the dirty and young bits from old_pud to the folio:

	old_pud = pudp_huge_clear_flush(vma, haddr, pud);

	if (!vma_is_dax(vma))
		return;

	page = pud_page(old_pud);
	folio = page_folio(page);

	if (!folio_test_dirty(folio) && pud_dirty(old_pud))
		folio_mark_dirty(folio);
	if (!folio_test_referenced(folio) && pud_young(old_pud))
		folio_set_referenced(folio);

On systems with FEAT_HAFDBS, couldn't a concurrent hardware access or dirty
state update between the non-atomic read of *pudp and pud_clear(pudp) be lost?

[ ... ]

> @@ -1443,6 +1485,15 @@ static inline pmd_t pmdp_establish(struct vm_area_struct *vma,
>  }
>  #endif
>  
> +#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)
> +{
> +	page_table_check_pud_set(vma->vm_mm, address, pudp, pud);
> +	return __pud(xchg_relaxed(&pud_val(*pudp), pud_val(pud)));
> +}
> +#endif

[Severity: High]
Is __HAVE_ARCH_PUDP_SET_WRPROTECT with pudp_set_wrprotect() also needed here,
matching pmdp_set_wrprotect() right above pmdp_establish()?

Without an architecture-specific pudp_set_wrprotect() wrapping
__ptep_set_wrprotect(), arm64 uses the generic fallback in
include/linux/pgtable.h:

static inline void pudp_set_wrprotect(struct mm_struct *mm,
				      unsigned long address, pud_t *pudp)
{
	pud_t old_pud = *pudp;

	set_pud_at(mm, address, pudp, pud_wrprotect(old_pud));
}

When fork() copies a COW PUD via copy_page_range() -> copy_p4d_range() ->
copy_pud_range() -> copy_huge_pud() -> pudp_set_wrprotect():

1. The non-atomic read of *pudp followed by set_pud_at() races with hardware
   AF/DBM updates (FEAT_HAFDBS), unlike __ptep_set_wrprotect() which uses a
   cmpxchg_relaxed() loop to atomically transfer hardware dirty state to
   PTE_DIRTY while setting PTE_RDONLY and clearing PTE_WRITE.

2. Updating a live valid PUD in place via set_pud_at() -> __set_ptes_anysz()
   calls __check_safe_pte_update() in arch/arm64/mm/mmu.c, which triggers
   VM_WARN_ONCE() when the entry is old or writable-clean on a live mm:

	VM_WARN_ONCE(!pte_young(pte),
		     "%s: racy access flag clearing: %s -> %s",
		     __func__, pte_str_old, pte_str);
	VM_WARN_ONCE(pte_write(old_pte) && !pte_dirty(pte),
		     "%s: racy dirty state clearing: %s -> %s",
		     __func__, pte_str_old, pte_str);

3. set_pud_at() also runs __sync_cache_and_tags() across PUD_SIZE >> PAGE_SHIFT
   (262,144) pages unnecessarily when only write-protecting an existing entry.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261028121404.853944-1-tujinjiang@huawei.com?part=3


      parent reply	other threads:[~2026-10-29 12:14 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-28 12:14 [PATCH RESEND 0/3] arm64: Add PUD THP support for DAX and pfnmap Jinjiang Tu
2026-10-28 12:14 ` [PATCH RESEND 1/3] mm: define generic pudp_invalidate() implementation Jinjiang Tu
2026-10-28 12:14 ` [PATCH RESEND 2/3] arm64: simplify pmd_special and pmd_mkspecial Jinjiang Tu
2026-10-28 12:14 ` [PATCH RESEND 3/3] arm64: Add PUD THP support for DAX and pfnmap Jinjiang Tu
2026-10-29  3:39   ` kernel test robot
2026-10-29 12:14   ` sashiko-bot [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-189265@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=akpm@linux-foundation.org \
    --cc=catalin.marinas@arm.com \
    --cc=dave.jiang@intel.com \
    --cc=david@kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=peterx@redhat.com \
    --cc=ryan.roberts@arm.com \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=sunnanyong@huawei.com \
    --cc=tujinjiang@huawei.com \
    --cc=wangkefeng.wang@huawei.com \
    --cc=will@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox