All of 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.