Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: mpenttil@redhat.com, linux-mm@kvack.org
Cc: dri-devel@lists.freedesktop.org, intel-xe@lists.freedesktop.org,
	"David Hildenbrand" <david@kernel.org>,
	"Jason Gunthorpe" <jgg@nvidia.com>,
	"Leon Romanovsky" <leonro@nvidia.com>,
	"Alistair Popple" <apopple@nvidia.com>,
	"Balbir Singh" <balbirs@nvidia.com>, "Zi Yan" <ziy@nvidia.com>,
	"Matthew Brost" <matthew.brost@intel.com>,
	"Andrew Morton" <akpm@linux-foundation.org>,
	"Lorenzo Stoakes" <ljs@kernel.org>,
	"Liam R. Howlett" <Liam.Howlett@oracle.com>,
	"Vlastimil Babka" <vbabka@suse.cz>,
	"Mike Rapoport" <rppt@kernel.org>,
	"Suren Baghdasaryan" <surenb@google.com>,
	"Michal Hocko" <mhocko@suse.com>, "Lyude Paul" <lyude@redhat.com>,
	"Danilo Krummrich" <dakr@kernel.org>
Subject: Re: [PATCH v16 04/12] mm/hmm: do the plumbing for HMM to participate in migration
Date: Thu, 22 Oct 2026 06:06:34 +0000	[thread overview]
Message-ID: <sashiko-outbox-181052@kernel.org> (raw)
In-Reply-To: <20261021060506.2377545-5-mpenttil@redhat.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] mm/hmm: broken migration entries in hmm_vma_handle_pte() and hmm_vma_walk_pmd()
- [High] mm/hmm: broken state and rollback in hmm_vma_walk_pmd() on retry after -EAGAIN
- [Medium] mm/hmm: hmm_vma_walk_pmd() aborts PMD walk and clobbers PFNs on split failure
--

commit 25901a209351c6985ba22f8dfc40a2db03ec4bdf
Author: Mika Penttilä <mpenttil@redhat.com>

mm/hmm: do the plumbing for HMM to participate in migration

This commit adds page table locking state and migration preparation plumbing
to hmm_range_fault() and its pagewalk callbacks in mm/hmm.c so that PTE and
PMD locks are held across entry handling and migration collection and dropped
before faulting.

> diff --git a/mm/hmm.c b/mm/hmm.c
> index 4e9542aedc253..119e70dbca56f 100644
> --- a/mm/hmm.c
> +++ b/mm/hmm.c

[ ... ]

> @@ -305,14 +360,25 @@ static int hmm_vma_handle_pte(struct mm_walk *walk, unsigned long addr,
>  			goto fault;
>  
>  		if (softleaf_is_migration(entry)) {
> -			pte_unmap(ptep);
> -			hmm_vma_walk->last = addr;
> -			migration_entry_wait(walk->mm, pmdp, addr);
> -			return -EBUSY;
> +			if (!hmm_select_migrate(range)) {
> +				HMM_ASSERT_UNLOCKED(hmm_vma_walk);
> +				pte_unmap(ptep);
> +				hmm_vma_walk->last = addr;
> +				migration_entry_wait(walk->mm, pmdp, addr);
> +				return -EBUSY;
> +			}
> +			return 0;
>  		}

[Severity: High]
In include/linux/hmm.h, the input request flags share the same bit positions
as the output flags (HMM_PFN_REQ_FAULT == HMM_PFN_VALID,
HMM_PFN_REQ_WRITE == HMM_PFN_WRITE, HMM_PFN_REQ_MIGRATE == HMM_PFN_MIGRATE),
and callers using range->default_flags with range->pfn_flags_mask == 0 are
not required to initialize range->hmm_pfns[] on input.

In hmm_vma_handle_pte(), the !pte_present(pte) path evaluates
required_fault = hmm_pte_need_fault(hmm_vma_walk, pfn_req_flags, 0) first:

	required_fault =
		hmm_pte_need_fault(hmm_vma_walk, pfn_req_flags, 0);
	if (!required_fault)
		goto out;

Because of that check, if (softleaf_is_migration(entry)) is only reached when
required_fault != 0 (HMM_PFN_REQ_FAULT was requested). When
hmm_select_migrate(range) is non-zero, returning 0 directly here skips both
migration_entry_wait() (which would require unlocking hmm_vma_walk->ptl first)
and the out: label that masks *hmm_pfn to HMM_PFN_INOUT_FLAGS:

out:
	*hmm_pfn = (*hmm_pfn & HMM_PFN_INOUT_FLAGS) | new_pfn_flags;
	return 0;

Won't returning 0 directly on a concurrent migration entry leave stale input
flags or uninitialized bits (such as HMM_PFN_REQ_FAULT / HMM_PFN_VALID and
HMM_PFN_REQ_MIGRATE / HMM_PFN_MIGRATE) in *hmm_pfn, pass a migration PTE into
hmm_vma_handle_migrate_prepare(), and allow hmm_range_fault() to succeed
without waiting for the migration entry to resolve despite HMM_PFN_REQ_FAULT?

Conversely, when HMM_PFN_REQ_FAULT is not set (such as the default in
migrate_vma_setup()), if (!required_fault) goto out runs before checking
softleaf_is_migration(entry). If a retry re-walks a migration PTE that was
already installed by this walk, won't jumping to out: clear *hmm_pfn (and
trigger rollback in the later patch "mm/hmm: implement rollback for device
page migration in HMM pagewalk") while leaving the migration PTE in the page
table?

[ ... ]

> @@ -468,46 +593,116 @@ static int hmm_vma_walk_pmd(pmd_t *pmdp,
>  {
>  	struct hmm_vma_walk *hmm_vma_walk = walk->private;
>  	struct hmm_range *range = hmm_vma_walk->range;
> -	unsigned long *hmm_pfns =
> -		&range->hmm_pfns[(start - range->start) >> PAGE_SHIFT];
>  	unsigned long npages = (end - start) >> PAGE_SHIFT;
> +	struct mm_struct *mm = walk->vma->vm_mm;
> +	unsigned long *hmm_pfns, *hmm_pfns_start;
> +	enum migrate_vma_info minfo;
>  	unsigned long addr = start;
> +	bool unmapped = false;
> +	unsigned long i;
>  	pte_t *ptep;
>  	pmd_t pmd;
> +	int r = 0;
>  
> +	minfo = hmm_select_migrate(range);
> +	hmm_pfns_start = &range->hmm_pfns[(start - range->start) >> PAGE_SHIFT];
>  again:
> -	pmd = pmdp_get_lockless(pmdp);
> -	if (pmd_none(pmd))
> -		return hmm_vma_walk_hole(start, end, -1, walk);
> +	hmm_pfns = &range->hmm_pfns[(addr - range->start) >> PAGE_SHIFT];
> +	hmm_vma_walk->ptelocked = false;
> +	hmm_vma_walk->pmdlocked = false;
> +
> +	if (minfo) {
> +		hmm_vma_walk->ptl = pmd_lock(mm, pmdp);
> +		hmm_vma_walk->pmdlocked = true;
> +		pmd = pmdp_get(pmdp);
> +	} else
> +		pmd = pmdp_get_lockless(pmdp);
> +
> +	if (pmd_none(pmd)) {
> +		r = hmm_vma_walk_hole(start, end, -1, walk);
> +
> +		if (hmm_vma_walk->pmdlocked) {
> +			spin_unlock(hmm_vma_walk->ptl);
> +			hmm_vma_walk->pmdlocked = false;
> +		}
> +		return r;
> +	}
>  
>  	if (thp_migration_supported() && pmd_is_migration_entry(pmd)) {
> -		if (hmm_range_need_fault(hmm_vma_walk, hmm_pfns, npages, 0)) {
> -			hmm_vma_walk->last = addr;
> -			pmd_migration_entry_wait(walk->mm, pmdp);
> -			return -EBUSY;
> +		if (!minfo) {
> +			if (hmm_range_need_fault(hmm_vma_walk, hmm_pfns_start, npages, 0)) {
> +				hmm_vma_walk->last = addr;
> +				pmd_migration_entry_wait(walk->mm, pmdp);
> +				return -EBUSY;
> +			}
> +			for (i = 0; start < end; start += PAGE_SIZE, i++)
> +				hmm_pfns_start[i] &= HMM_PFN_INOUT_FLAGS;
> +		}
> +
> +		if (hmm_vma_walk->pmdlocked) {
> +			spin_unlock(hmm_vma_walk->ptl);
> +			hmm_vma_walk->pmdlocked = false;
>  		}
> -		return hmm_pfns_fill(start, end, range, 0);
> +
> +		return 0;
>  	}

[Severity: High]
Similarly, in hmm_vma_walk_pmd(), when thp_migration_supported() &&
pmd_is_migration_entry(pmd) is true and minfo != 0, both the
hmm_range_need_fault() check and the loop masking hmm_pfns_start[] to
HMM_PFN_INOUT_FLAGS are skipped by if (!minfo).

When a concurrent PMD migration entry is encountered during a migration walk,
doesn't skipping that block leave hmm_pfns_start[0..npages - 1] unmasked and
uninitialized, and also fail to unlock hmm_vma_walk->ptl and wait in
pmd_migration_entry_wait() when a fault was requested? Neither this path nor
the PTE migration entry path in hmm_vma_handle_pte() appears to be updated in
the subsequent patches in the series.

> -	if (!pmd_present(pmd))
> -		return hmm_vma_handle_absent_pmd(walk, start, end, hmm_pfns,
> -						 pmd);
> +	if (pmd_trans_huge(pmd) || !pmd_present(pmd)) {
> +		if (!pmd_present(pmd)) {
> +			r = hmm_vma_handle_absent_pmd(walk, start, end, hmm_pfns_start,
> +						      pmd);
> +			// If not migrating we are done
> +			if (r || !minfo) {
> +				if (hmm_vma_walk->pmdlocked) {
> +					spin_unlock(hmm_vma_walk->ptl);
> +					hmm_vma_walk->pmdlocked = false;
> +				}
> +				return r;
> +			}
> +		}
>  
> -	if (pmd_trans_huge(pmd)) {
> -		/*
> -		 * No need to take pmd_lock here, even if some other thread
> -		 * is splitting the huge pmd we will get that event through
> -		 * mmu_notifier callback.
> -		 *
> -		 * So just read pmd value and check again it's a transparent
> -		 * huge or device mapping one and compute corresponding pfn
> -		 * values.
> -		 */
> -		pmd = pmdp_get_lockless(pmdp);
> -		if (!pmd_trans_huge(pmd))
> -			goto again;
> +		if (pmd_trans_huge(pmd)) {
> +			/*
> +			 * No need to take pmd_lock here if not migrating,
> +			 * even if some other thread is splitting the huge
> +			 * pmd we will get that event through mmu_notifier callback.
> +			 *
> +			 * So just read pmd value and check again it's a transparent
> +			 * huge or device mapping one and compute corresponding pfn
> +			 * values.
> +			 */
> +
> +			if (!minfo) {
> +				pmd = pmdp_get_lockless(pmdp);
> +				if (!pmd_trans_huge(pmd))
> +					goto again;
> +			}
>  
> -		return hmm_vma_handle_pmd(walk, addr, end, hmm_pfns, pmd);
> +			r = hmm_vma_handle_pmd(walk, start, end, hmm_pfns_start, pmd);
> +
> +			// If not migrating we are done
> +			if (r || !minfo) {
> +				if (hmm_vma_walk->pmdlocked) {
> +					spin_unlock(hmm_vma_walk->ptl);
> +					hmm_vma_walk->pmdlocked = false;
> +				}
> +				return r;
> +			}
> +		}
> +
> +		r = hmm_vma_handle_migrate_prepare_pmd(walk, pmdp, start, end, hmm_pfns_start);
> +
> +		if (hmm_vma_walk->pmdlocked) {
> +			spin_unlock(hmm_vma_walk->ptl);
> +			hmm_vma_walk->pmdlocked = false;
> +		}
> +
> +		return r;
> +	}
> +
> +	if (hmm_vma_walk->pmdlocked) {
> +		spin_unlock(hmm_vma_walk->ptl);
> +		hmm_vma_walk->pmdlocked = false;
>  	}
>  
>  	/*
> @@ -517,24 +712,64 @@ static int hmm_vma_walk_pmd(pmd_t *pmdp,
>  	 * recover.
>  	 */
>  	if (pmd_bad(pmd)) {
> -		if (hmm_range_need_fault(hmm_vma_walk, hmm_pfns, npages, 0))
> +		if (hmm_range_need_fault(hmm_vma_walk, hmm_pfns_start, npages, 0))
>  			return -EFAULT;
> -		return hmm_pfns_fill(start, end, range, HMM_PFN_ERROR);
> +		return hmm_pfns_fill(start, end, hmm_vma_walk, HMM_PFN_ERROR);
>  	}
>  
> -	ptep = pte_offset_map(pmdp, addr);
> -	if (!ptep)
> +	if (minfo) {
> +		ptep = pte_offset_map_lock(mm, pmdp, addr, &hmm_vma_walk->ptl);
> +		if (ptep)
> +			hmm_vma_walk->ptelocked = true;
> +	} else {
> +		ptep = pte_offset_map(pmdp, addr);
> +	}
> +	if (!ptep) {
> +		addr = start;
>  		goto again;
> +	}
> +
>  	for (; addr < end; addr += PAGE_SIZE, ptep++, hmm_pfns++) {
> -		int r;
>  
>  		r = hmm_vma_handle_pte(walk, addr, end, pmdp, ptep, hmm_pfns);
>  		if (r) {
> -			/* hmm_vma_handle_pte() did pte_unmap() */
> +			/* hmm_vma_handle_pte() did pte_unmap() / pte_unmap_unlock */
> +			if (unmapped)
> +				flush_tlb_range(walk->vma, start, addr);
>  			return r;
>  		}
> +
> +		r = hmm_vma_handle_migrate_prepare(walk, pmdp, ptep, addr, hmm_pfns, &unmapped);
> +		if (r == -EAGAIN) {
> +			HMM_ASSERT_UNLOCKED(hmm_vma_walk);
> +			if (unmapped) {
> +				flush_tlb_range(walk->vma, start, addr);
> +				unmapped = false;
> +			}
> +			goto again;
> +		}

[Severity: High]
When hmm_vma_handle_migrate_prepare() (populated in the later patch "mm/hmm:
migrate collection in HMM pagewalk - pte level") encounters a PTE-mapped large
folio at addr > start after order-0 pages in [start, addr) have already been
collected, migrate_vma_split_folio() drops the PTE lock and returns -EAGAIN,
jumping to again: with addr > start.

Even with the later patch "mm/hmm: implement rollback for device page
migration in HMM pagewalk", can jumping back to again: with addr > start leave
stale collected state in [start, addr) or leak folio locks and references?

1. If pte_offset_map_lock() returns null (!ptep) at addr > start because the
   PTE table disappeared while the PTE lock was dropped, the rollback patch
   calls hmm_vma_handle_migrate_prepare_rollback(walk->private, addr, end,
   hmm_pfns) on the unwalked tail [addr, end) instead of rolling back the
   already-collected prefix [start, addr) at hmm_pfns_start before resetting
   addr = start.

2. If the PTE page table was removed or replaced while the PTE lock was
   dropped in migrate_vma_split_folio() (or after !ptep jumps to again:), the
   PMD-level paths at again: do not roll back [start, addr) when
   pmd_is_migration_entry(pmd) is true (which returns 0 with [start, addr)
   still marked HMM_PFN_VALID | HMM_PFN_MIGRATE and addr still marked
   HMM_PFN_VALID from hmm_vma_handle_pte()), or when hmm_vma_walk_hole(),
   hmm_vma_handle_absent_pmd(), hmm_vma_handle_pmd(), or pmd_bad() returns
   HMM_FAULT_PENDING or -EFAULT. Likewise, if the PTE table was replaced by a
   new PTE table while the lock was dropped, again: resumes at addr > start
   without rolling back or re-walking [start, addr).

> +		if (r) {
> +			/* A non -EAGAIN error here means migrate_vma_split_folio()
> +			 * already dropped the PTE lock and cleared ptelocked.
> +			 */
> +			HMM_ASSERT_UNLOCKED(hmm_vma_walk);
> +			if (unmapped)
> +				flush_tlb_range(walk->vma, start, addr);
> +			hmm_pfns_fill(addr, end, hmm_vma_walk, HMM_PFN_ERROR);
> +			return 0;
> +		}

[Severity: Medium]
When hmm_vma_handle_migrate_prepare() returns a non-EAGAIN error in the later
patch "mm/hmm: migrate collection in HMM pagewalk - pte level" because
migrate_vma_split_folio() failed to split a PTE-mapped large folio, why does
hmm_vma_walk_pmd() fill [addr, end) with HMM_PFN_ERROR and return 0?

First, hmm_vma_handle_pte() has already recorded the valid PFN and
HMM_PFN_VALID in *hmm_pfns for addr. Overwriting addr with HMM_PFN_ERROR means
callers such as nouveau_dmem_migrate_fault() (in "drm/nouveau/svm: add support
for migrate on fault") that fall back to mapping present CPU pages in place
when migration does not happen cannot use the PFN, unlike folio_trylock() or
folio_try_share_anon_rmap_pte() failures which preserve the valid PFN without
HMM_PFN_MIGRATE.

Second, returning 0 immediately aborts the loop for the rest of the PMD range
[addr + PAGE_SIZE, end), skipping both faulting (hmm_vma_handle_pte()) and
migration collection (hmm_vma_handle_migrate_prepare()) for all remaining PTEs
in that PMD and returning 0 from hmm_range_fault() with HMM_PFN_ERROR entries
even when HMM_PFN_REQ_FAULT was requested.

>  	}
> -	pte_unmap(ptep - 1);
> +
> +	if (unmapped)
> +		flush_tlb_range(walk->vma, start, addr);
> +
> +	if (hmm_vma_walk->ptelocked) {
> +		pte_unmap_unlock(ptep - 1, hmm_vma_walk->ptl);
> +		hmm_vma_walk->ptelocked = false;
> +	} else {
> +		pte_unmap(ptep - 1);
> +	}
> +
>  	return 0;
>  }

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261021060506.2377545-1-mpenttil@redhat.com?part=4


  reply	other threads:[~2026-10-22  6:06 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21  6:04 [PATCH v16 00/12] migrate on fault for device pages mpenttil
2026-10-21  6:04 ` [PATCH v16 01/12] mm/Kconfig: changes for " mpenttil
2026-10-21  6:04 ` [PATCH v16 02/12] mm: add helper to convert HMM pfn to migrate pfn mpenttil
2026-10-21  6:04 ` [PATCH v16 03/12] mm/hmm: preparations for HMM to participate in migration mpenttil
2026-10-21  7:17   ` Balbir Singh
2026-10-21  8:11     ` Mika Penttilä
2026-10-22  6:06   ` sashiko-bot
2026-10-21  6:04 ` [PATCH v16 04/12] mm/hmm: do the plumbing " mpenttil
2026-10-22  6:06   ` sashiko-bot [this message]
2026-10-21  6:04 ` [PATCH v16 05/12] mm/hmm: migrate collection in HMM pagewalk - pte level mpenttil
2026-10-21  6:56   ` Balbir Singh
2026-10-21  7:09     ` Mika Penttilä
2026-10-22  6:06   ` sashiko-bot
2026-10-21  6:05 ` [PATCH v16 06/12] mm/hmm: migrate collection in HMM pagewalk - pmd level mpenttil
2026-10-22  6:06   ` sashiko-bot
2026-10-21  6:05 ` [PATCH v16 07/12] mm/hmm: add lazy MMU mode support for migration in HMM pagewalk mpenttil
2026-10-21  6:05 ` [PATCH v16 08/12] mm/hmm: implement rollback for device page " mpenttil
2026-10-22  6:06   ` sashiko-bot
2026-10-21  6:05 ` [PATCH v16 09/12] mm: enable device page migration from " mpenttil
2026-10-21  6:19   ` Balbir Singh
2026-10-21  6:30     ` Mika Penttilä
2026-10-22  6:06   ` sashiko-bot
2026-10-21  6:05 ` [PATCH v16 10/12] lib/test_hmm: add a new testcase for the migrate on fault mpenttil
2026-10-22  6:06   ` sashiko-bot
2026-10-21  6:05 ` [PATCH v16 11/12] Documentation/mm/hmm: document migration through hmm_range_fault() mpenttil
2026-10-22  6:06   ` sashiko-bot
2026-10-21  6:05 ` [PATCH v16 12/12] drm/nouveau/svm: add support for migrate on fault mpenttil
2026-10-22  6:06   ` sashiko-bot

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-181052@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=Liam.Howlett@oracle.com \
    --cc=akpm@linux-foundation.org \
    --cc=apopple@nvidia.com \
    --cc=balbirs@nvidia.com \
    --cc=dakr@kernel.org \
    --cc=david@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=jgg@nvidia.com \
    --cc=leonro@nvidia.com \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=lyude@redhat.com \
    --cc=matthew.brost@intel.com \
    --cc=mhocko@suse.com \
    --cc=mpenttil@redhat.com \
    --cc=rppt@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=surenb@google.com \
    --cc=vbabka@suse.cz \
    --cc=ziy@nvidia.com \
    /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