Linux Documentation
 help / color / mirror / Atom feed
* Re: [PATCH 11/14] userfaultfd: add UFFD_FEATURE_RWP_ASYNC for async fault resolution
From: Kiryl Shutsemau @ 2026-05-01 10:49 UTC (permalink / raw)
  To: akpm, rppt, peterx, david
  Cc: ljs, surenb, vbabka, Liam.Howlett, ziy, corbet, skhan, seanjc,
	pbonzini, jthoughton, aarcange, sj, usama.arif, linux-mm,
	linux-kernel, linux-doc, linux-kselftest, kvm, kernel-team
In-Reply-To: <20260427114607.4068647-12-kas@kernel.org>

sashiko.dev -- https://sashiko.dev/#/patchset/20260427114607.4068647-1-kas@kernel.org -- wrote:
> commit 069c98442d3532bbf015817229b8db505210e97d
> Author: Kiryl Shutsemau (Meta) <kas@kernel.org>
> Subject: userfaultfd: add UFFD_FEATURE_RWP_ASYNC for async fault resolution
[ ... ]
> Does this sequence create a clean but writable PMD?
> [ ... ] Could this result in modified data being silently discarded
> instead of written back during page reclaim?
> Usually, code paths establishing writable entries set the dirty bit
> together with the write bit, for instance by using
> pmd_mkwrite(pmd_mkdirty(pmd)).

The pattern intentionally mirrors do_numa_page() / numa_rebuild_single_mapping()
which has been in the kernel for years using the same sequence:

  pte = pte_modify(old_pte, vma->vm_page_prot);
  pte = pte_mkyoung(pte);
  if (writable)
          pte = pte_mkwrite(pte, vma);

with no pte_mkdirty(). The "writable" decision is fenced by
can_change_pte_writable(), which keeps the result safe in both the
shared and private cases:

  - Private (can_change_private_pte_writable): only allows the upgrade
    for PageAnonExclusive pages.

  - Shared (can_change_shared_pte_writable): returns true only when
    pte_dirty(pte). The dirty bit lives in _PAGE_CHG_MASK, so the
    earlier pte_modify(pte, vma->vm_page_prot) preserves it; the final
    PTE is writable + dirty.

The same applies to the PMD path through can_change_pmd_writable().
There is no "clean + writable" PTE/PMD escaping either branch.

> Similarly, does this create a clean but writable PTE?
> If the PTE is made writable without calling pte_mkdirty(), it might
> violate the invariant that writable PTEs must be dirty, [ ... ]

The "writable PTEs must be dirty" invariant is not a kernel-wide rule;
it depends on the architecture and the code path. Where the kernel
relies on pte_mkdirty() being called explicitly, can_change_pte_writable()
returns false and this path is not taken. do_uffd_rwp() is the same
shape as do_numa_page() and inherits its correctness arguments.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov

^ permalink raw reply

* Re: [PATCH 10/14] mm/pagemap: add PAGE_IS_ACCESSED for RWP tracking
From: Kiryl Shutsemau @ 2026-05-01 10:41 UTC (permalink / raw)
  To: akpm, rppt, peterx, david
  Cc: ljs, surenb, vbabka, Liam.Howlett, ziy, corbet, skhan, seanjc,
	pbonzini, jthoughton, aarcange, sj, usama.arif, linux-mm,
	linux-kernel, linux-doc, linux-kselftest, kvm, kernel-team
In-Reply-To: <20260427114607.4068647-11-kas@kernel.org>

sashiko.dev -- https://sashiko.dev/#/patchset/20260427114607.4068647-1-kas@kernel.org -- wrote:
> commit 6d6b8f986b2959f8ae0932deccc5afa4ec485813
> Author: Kiryl Shutsemau (Meta) <kas@kernel.org>
> Subject: mm/pagemap: add PAGE_IS_ACCESSED for RWP tracking
[ ... ]
> Does this new PAGE_IS_ACCESSED tracking correctly support the atomic
> read-and-reset workflow (PM_SCAN_WP_MATCHING) for RWP VMAs?
[ ... ]
> Since WP and RWP are mutually exclusive per VMA, userfaultfd_wp_async() and
> userfaultfd_wp_use_markers() will return false for RWP VMAs, causing
> pagemap_scan_test_walk() to return 1 and skip the VMA.
[ ... ]
> Because these loops hardcode a check against PAGE_IS_WRITTEN, and RWP VMAs
> now populate PAGE_IS_ACCESSED instead, this check will always evaluate to
> true for RWP. As a result, it looks like make_uffd_wp_pte() and
> make_uffd_wp_pmd() will be silently skipped, preventing the atomic
> reset of the tracking bits.
> Would it make sense to update both the wp_allowed check and the hardcoded
> PAGE_IS_WRITTEN checks to accommodate PAGE_IS_ACCESSED for RWP VMAs?

PM_SCAN_WP_MATCHING is the atomic read-and-reset flavour and is
implemented for the WP marker only.

The silent-skip on PM_SCAN_WP_MATCHING + RWP VMA is not intended
Will fold this fixup

        if (userfaultfd_rwp(vma) && (p->arg.flags & PM_SCAN_WP_MATCHING))
                return -EINVAL;

We can add similar operation for RWP later if there's a use-case.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov

^ permalink raw reply

* Re: [PATCH v4 3/3] Documentation: deprecated.rst: kmalloc-family: mark argument as optional
From: Manuel Ebner @ 2026-05-01  9:19 UTC (permalink / raw)
  To: SeongJae Park
  Cc: Jonathan Corbet, Shuah Khan, linux-doc, Kees Cook, linux-kernel,
	workflows, linux-mm, Geert Uytterhoeven
In-Reply-To: <20260430010332.114100-1-sj@kernel.org>

On Wed, 2026-04-29 at 18:03 -0700, SeongJae Park wrote:
> On Wed, 29 Apr 2026 09:27:04 +0200 Manuel Ebner <manuelebner@mailbox.org>
> wrote:
> 
> > put the optional argument (gfp) in square brackets
> > add default value = GFP_KERNEL
> > 
> > eg. ptr = kmalloc_obj(*ptr, gfp);
> >  -> ptr = kmalloc_obj(*ptr [, gfp] );
> > 
> > Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
> 
> I have a trivial question below, but because it is trivial,
> 
> Acked-by: SeongJae Park <sj@kernel.org>
> 
> > ---
> >  Documentation/process/deprecated.rst | 15 ++++++++-------
> >  1 file changed, 8 insertions(+), 7 deletions(-)
> > 
> > diff --git a/Documentation/process/deprecated.rst
> > b/Documentation/process/deprecated.rst
> > index fed56864d036..ac75b7ecac47 100644
> > --- a/Documentation/process/deprecated.rst
> > +++ b/Documentation/process/deprecated.rst
> > @@ -392,13 +392,14 @@ allocations. For example, these open coded
> > assignments::
> >  
> >  become, respectively::
> >  
> > -	ptr = kmalloc_obj(*ptr, gfp);
> > -	ptr = kzalloc_obj(*ptr, gfp);
> > -	ptr = kmalloc_objs(*ptr, count, gfp);
> > -	ptr = kzalloc_objs(*ptr, count, gfp);
> > -	ptr = kmalloc_flex(*ptr, flex_member, count, gfp);
> > -	__auto_type ptr = kmalloc_obj(struct foo, gfp);
> > -
> > +	ptr = kmalloc_obj(*ptr [, gfp] );
> > +	ptr = kzalloc_obj(*ptr [, gfp] );
> > +	ptr = kmalloc_objs(*ptr, count [, gfp] );
> > +	ptr = kzalloc_objs(*ptr, count [, gfp] );
> > +	ptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );
> > +	__auto_type ptr = kmalloc_obj(struct foo [, gfp] );
> > +
> > +The argument gfp is optional, the default value is GFP_KERNEL.
> >  If `ptr->flex_member` is annotated with __counted_by(), the allocation
> >  will automatically fail if `count` is larger than the maximum
> >  representable value that can be stored in the counter member associated
> 
> Like 'ptr->flex_member' and 'count', why don't you enclose 'gfp' and
> 'GFP_KERNEL' with backticks ('`')?

I didn't know what ` is doing, so didn't consider it. It makes sense to
enclose these two.
should __counted_by() be enclosed aswell?

> Thanks,
> SJ

Thanks
 Manuel
---
The possibility of getting blamed has to be earned.


^ permalink raw reply

* Re: [PATCH v4 1/3] Documentation: adopt new coding style of type-aware kmalloc-family
From: Manuel Ebner @ 2026-05-01  9:12 UTC (permalink / raw)
  To: SeongJae Park
  Cc: Jonathan Corbet, Shuah Khan, linux-doc, Kees Cook, linux-kernel,
	workflows, linux-sound, linux-media, linux-mm
In-Reply-To: <20260430010013.113971-1-sj@kernel.org>

On Wed, 2026-04-29 at 18:00 -0700, SeongJae Park wrote:
> On Wed, 29 Apr 2026 17:53:36 -0700 SeongJae Park <sj@kernel.org> wrote:
> 
> > On Wed, 29 Apr 2026 09:14:44 +0200 Manuel Ebner <manuelebner@mailbox.org>
> > wrote:
> > 
> > > Update the documentation to reflect new type-aware kmalloc-family as
> > > suggested in commit 2932ba8d9c99 ("slab: Introduce kmalloc_obj()
> > > and family")
> > > 
> > > ptr = kmalloc(sizeof(*ptr), gfp);
> > >  -> ptr = kmalloc_obj(*ptr);
> > > ptr = kmalloc(sizeof(struct some_obj_name), gfp);
> > >  -> ptr = kmalloc_obj(*ptr);
> > > ptr = kzalloc(sizeof(*ptr), gfp);
> > >  -> ptr = kzalloc_obj(*ptr);
> > > ptr = kmalloc_array(count, sizeof(*ptr), gfp);
> > >  -> ptr = kmalloc_objs(*ptr, count);
> > > ptr = kcalloc(count, sizeof(*ptr), gfp);
> > >  -> ptr = kzalloc_objs(*ptr, count);
> 
> Forgot asking this, sorry.  Shouldn't 'gfp' parameters be kept?

Yes, i should have kept it, like so:

eg. ptr = kmalloc_obj(*ptr, gfp);
 -> ptr = kmalloc_obj(*ptr [, gfp] );

same in [Patch 2/3]

> > 

> > > Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
> > 
> > Acked-by: SeongJae Park <sj@kernel.org>
> 
> My Acked-by: is still valid regardless of your answer to my trivial question.

Thanks
 Manuel

> 
> Thanks,
> SJ
> 
> [...]


^ permalink raw reply

* Re: [RFC] Interest in contributing to Linux Kernel documentation (French translation)
From: Jonathan Corbet @ 2026-05-01  9:06 UTC (permalink / raw)
  To: Dewey, linux-doc
In-Reply-To: <CADWiQPK3kC5ymXsdYT6tc7qH47THfY=LpBSW3=dcGNO2Fi_p_A@mail.gmail.com>

Dewey <thawentha4@gmail.com> writes:

> Hello everyone,
> My name is Dewey, I am a software enthusiast with an interest in the
> Linux kernel. I am reaching out to the list to see if there is any
> interest or an established process for improving the documentation by
> adding or maintaining translations, specifically for French.
> I understand that the official documentation is maintained in English
> and that keeping translations in sync is a significant challenge.
> However, I would like to offer my time to help, whether it involves
> translating specific sections or helping with the automation of
> documentation generation.
> Could you let me know if there are any current initiatives regarding
> translations, or if there is a preferred way to contribute in this
> area?

There are a number of active translation efforts; you can find them all
under Documentation/translations/.  There is currently no French
translation, though.

Starting a new translation is not a small effort; it requires a
significant commitment of time to keep up as the documentation evolves.
This is generally not a project that a single person can be expected to
sustain over a long period.  I do not want to discourage you too
severely, but I do want you to be aware that this would not be a
drive-by project.

"Automation of documentation generation" is a bit of a worrisome phrase
here.  Kernel documentation is for humans, and I don't see much value in
shoveling a lot of LLM output into the kernel tree.  If your plan is to
upstream a bunch of machine-generated translations, I would suggest
looking for another project.

Thanks,

jon

^ permalink raw reply

* [PATCH net-next v4 1/4] net: add dev->bql flag to allow BQL sysfs for IFF_NO_QUEUE devices
From: hawk @ 2026-05-01  7:16 UTC (permalink / raw)
  To: netdev
  Cc: hawk, kernel-team, Jonas Köppeler, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Jonathan Corbet, Shuah Khan, Kuniyuki Iwashima,
	Stanislav Fomichev, Christian Brauner, Yury Norov,
	Frederic Weisbecker, Yajun Deng, linux-doc, linux-kernel
In-Reply-To: <20260501071633.644353-1-hawk@kernel.org>

From: Jesper Dangaard Brouer <hawk@kernel.org>

Virtual devices with IFF_NO_QUEUE or lltx are excluded from BQL sysfs
by netdev_uses_bql(), since they traditionally lack real hardware
queues. However, some virtual devices like veth implement a real
ptr_ring FIFO with NAPI processing and benefit from BQL to limit
in-flight bytes and reduce latency.

Add a per-device 'bql' bitfield boolean in the priv_flags_slow section
of struct net_device. When set, it overrides the IFF_NO_QUEUE/lltx
exclusion and exposes BQL sysfs entries (/sys/class/net/<dev>/queues/
tx-<n>/byte_queue_limits/). The flag is still gated on CONFIG_BQL.

This allows drivers that use BQL despite being IFF_NO_QUEUE to opt in
to sysfs visibility for monitoring and debugging.

Signed-off-by: Jesper Dangaard Brouer <hawk@kernel.org>
Tested-by: Jonas Köppeler <j.koeppeler@tu-berlin.de>
---
 Documentation/networking/net_cachelines/net_device.rst | 1 +
 include/linux/netdevice.h                              | 2 ++
 net/core/net-sysfs.c                                   | 8 +++++++-
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/Documentation/networking/net_cachelines/net_device.rst b/Documentation/networking/net_cachelines/net_device.rst
index 1c19bb7705df..b775d3235a2d 100644
--- a/Documentation/networking/net_cachelines/net_device.rst
+++ b/Documentation/networking/net_cachelines/net_device.rst
@@ -170,6 +170,7 @@ unsigned_long:1                     see_all_hwtstamp_requests
 unsigned_long:1                     change_proto_down
 unsigned_long:1                     netns_immutable
 unsigned_long:1                     fcoe_mtu
+unsigned_long:1                     bql                                                                 netdev_uses_bql(net-sysfs.c)
 struct list_head                    net_notifier_list
 struct macsec_ops*                  macsec_ops
 struct udp_tunnel_nic_info*         udp_tunnel_nic_info
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 0e1e581efc5a..405bdf9172ca 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2065,6 +2065,7 @@ enum netdev_reg_state {
  *	@change_proto_down: device supports setting carrier via IFLA_PROTO_DOWN
  *	@netns_immutable: interface can't change network namespaces
  *	@fcoe_mtu:	device supports maximum FCoE MTU, 2158 bytes
+ *	@bql:		device uses BQL (DQL sysfs) despite having IFF_NO_QUEUE
  *
  *	@net_notifier_list:	List of per-net netdev notifier block
  *				that follow this device when it is moved
@@ -2479,6 +2480,7 @@ struct net_device {
 	unsigned long		change_proto_down:1;
 	unsigned long		netns_immutable:1;
 	unsigned long		fcoe_mtu:1;
+	unsigned long		bql:1;
 
 	struct list_head	net_notifier_list;
 
diff --git a/net/core/net-sysfs.c b/net/core/net-sysfs.c
index 3318b5666e43..82833e5dae03 100644
--- a/net/core/net-sysfs.c
+++ b/net/core/net-sysfs.c
@@ -1945,10 +1945,16 @@ static const struct kobj_type netdev_queue_ktype = {
 
 static bool netdev_uses_bql(const struct net_device *dev)
 {
+	if (!IS_ENABLED(CONFIG_BQL))
+		return false;
+
+	if (dev->bql)
+		return true;
+
 	if (dev->lltx || (dev->priv_flags & IFF_NO_QUEUE))
 		return false;
 
-	return IS_ENABLED(CONFIG_BQL);
+	return true;
 }
 
 static int netdev_queue_add_kobject(struct net_device *dev, int index)
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH 5/5] mm: support choosing to do THP COW for anonymous pmd entry.
From: David Hildenbrand (Arm) @ 2026-05-01  7:11 UTC (permalink / raw)
  To: Luka Bai, linux-mm
  Cc: Jonathan Corbet, Shuah Khan, Andrew Morton, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jann Horn, Arnd Bergmann,
	Kairui Song, linux-kernel, linux-arch, linux-doc, Luka Bai
In-Reply-To: <20260501-thp_cow-v1-5-005377483738@tencent.com>


> -	/*
> -	 * See do_wp_page(): we can only reuse the folio exclusively if
> -	 * there are no additional references. Note that we always drain
> -	 * the LRU cache immediately after adding a THP.
> -	 */
> -	if (folio_ref_count(folio) >
> -			1 + folio_test_swapcache(folio) * folio_nr_pages(folio))
> -		goto unlock_fallback;
>  	if (folio_test_swapcache(folio))

I don't see why you would want to remove this check, really. Instead of
"fallback", you might want to try copying the PMD.

-- 
Cheers,

David

^ permalink raw reply

* Re: [PATCH 0/5] mm: Support selecting doing direct COW for anonymous pmd entry
From: David Hildenbrand (Arm) @ 2026-05-01  7:07 UTC (permalink / raw)
  To: Luka Bai, linux-mm
  Cc: Jonathan Corbet, Shuah Khan, Andrew Morton, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jann Horn, Arnd Bergmann,
	Kairui Song, linux-kernel, linux-arch, linux-doc, Luka Bai
In-Reply-To: <20260501-thp_cow-v1-0-005377483738@tencent.com>

On 5/1/26 07:55, Luka Bai wrote:

Hi,

> Copy on write support for anonymous pmd level THP is simple right now:
> firstly we'll check whether the folio can be exclusively used by the
> faulting process, if we can (when the ref of the folio is only 1 after
> trying to free swapcache or the page flag AnonExclusive is setup) we'll
> directly use it with few further handling. If we cannot, then we'll
> split the pmd into 512 4K ptes, and do copy on write only for the
> specific 4K page that we faulted on.
> 
> This logic is truly memory efficient since for most workloads we don't
> want to allocate 2M new memory simply on a small write. However, it also
> makes the original 2M page for the process suddenly splitted on a
> write which will generate some performance thrashing. For example, if
> process A and process B share an anonymous 2M pmd, if process B chooses
> to do a writing, then its page table mapping will be changed from 1
> pmd entry into 512 4K pte entries at once, so the tlb benifit will
> suddenly just "vanish" for process B, which sometimes may cause a
> observable performance degeneration. After that, we can only wait for
> khugepaged to do the collapse for this area and merge the pmd back, which
> is not easy to happen.

You probably know that, historically, we did exactly what you describe in this
patch set. It was rather bad regarding memory waste and COW latency, so we
switched to the current model.

Note that there was a recent related discussion for executable, which was rejected:

https://lore.kernel.org/r/20251226100337.4171191-1-zhangqilong3@huawei.com

> 
> In addition to the problem above, this logic can also generate some
> deficiency for THP itself. Currently THP is just a "best-effort" choice
> with no "certainty". THP is easily splitted into multiple small pages
> on common calling path like reclaiming, COW. A transparent splitting
> can cause throughput fluctuation for some workloads. For these workloads,
> we may want to give THP some "certainty" just like hugetlbfs,

There are no such guarantees, though. And We wouldn't want to commit to any such
guarantees today. For example, simple page migration can split the folio.
Allocation failures will fallback to small pages etc.

If you need guarantees, use hugetlb for now.

> The effect
> we want is: after some customized setup, if only the system has usable
> folio, and the virtual memory alignment permits (or we setup to), we can
> make sure we always use THP for it, the system will never split it except
> the user wants to do so.
> 
> This patchset is about both two things above, firstly we add pmd level
> THP COW support by revising the code in do_huge_pmd_wp_page, we added
> switch for it because different workloads may need different resources,

The switch is bad, and we won't accept any toggle like that. A system-wide
setting does not make sense for such behavior.

A per-VMA flag? Maybe, but I expect pushback as well, as it is way too specific.
So we'd have to find some concept that abstracts these semantics. But I expect
pushback as well.

We messed up enough with toggles in THP space, unfortunately.

Also, anything that only works for PMD-sized THPs is a warning sign in 2026 :)

You don't really raise any concrete use cases or performance numbers for these
use cases. Some details about applications that use fork() and rely on such
behavior would be helpful.

Note that an application that does fork() could use MADV_COLLAPSE after fork()
to make sure that it immediately gets THPs back.

There is also the option to just use MADV_DONTFORK to not even share ranges with
a child process in the first place, avoiding page copies entirely.

-- 
Cheers,

David

^ permalink raw reply

* Re: [PATCH 00/11] mm/damon: introduce DAMOS failed region quota charge ratio
From: David Hildenbrand (Arm) @ 2026-05-01  6:49 UTC (permalink / raw)
  To: SeongJae Park
  Cc: Andrew Morton, Liam R. Howlett, Brendan Higgins, David Gow,
	Jonathan Corbet, Lorenzo Stoakes, Michal Hocko, Mike Rapoport,
	Shuah Khan, Shuah Khan, Suren Baghdasaryan, Vlastimil Babka,
	damon, kunit-dev, linux-doc, linux-kernel, linux-kselftest,
	linux-mm
In-Reply-To: <20260501015604.83041-1-sj@kernel.org>

On 5/1/26 03:56, SeongJae Park wrote:
> On Tue, 28 Apr 2026 08:24:24 -0700 SeongJae Park <sj@kernel.org> wrote:
> 
>> Hello Andrew,
>>
>> On Tue, 28 Apr 2026 07:48:37 -0700 Andrew Morton <akpm@linux-foundation.org> wrote:
>>
>>>
>>>
>>> Add, thanks.
>>>
>>> As mentioned provately, Sashiko claims to have found things which it
>>> didn't see in the RFC.
>>>
>>> 	https://sashiko.dev/#/patchset/20260428013402.115171-1-sj@kernel.org
>>
>> TL; DR: I find no blocker for this patch series from the Sashiko reviews.
>>
>> Now sashiko replies its reviews for DAMON patches to authors and
>> damon@lists.linux.dev.  So I replied [1,2,3] my review of the reviews to those
>> on damon@lists.linux.dev mailing list.  As I mentioned on the TL;DR, I find no
>> blocker for this series.
>>
>> And I think you didn't see those because those are sent to only authors and
>> damon@lists.linux.dev.
>>
>> I nowadays reply-all to original recipients only if Sashiko found a blocker.  I
>> will also add short notice for non-RFC patches if Sashiko found zero issue.
> 
> ... Now I think the short notice is only redundant and silly, as the full
> review is available on damon@ list and the one who primarily interested in
> (Andrew) understands that.  I feel like the short-notice reply-all only
> increase unnecessary traffic and my redundant typing.  I will not do the short
> notice broadcasting, unless someone makes a diffeernt voice.

For Damon Andrew should for now just trust your ACKs. If it has your ACK, it's
good to go.

In the future, I expect you would pick up the patches yourself, which is where
you as the component maintainer would look for any blockers.

So for Damon patches I don't think we need the AI review notices from Andrew.

-- 
Cheers,

David

^ permalink raw reply

* [PATCH 5/5] mm: support choosing to do THP COW for anonymous pmd entry.
From: Luka Bai @ 2026-05-01  5:55 UTC (permalink / raw)
  To: linux-mm
  Cc: Jonathan Corbet, Shuah Khan, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
	Arnd Bergmann, Kairui Song, linux-kernel, linux-arch, linux-doc,
	Luka Bai
In-Reply-To: <20260501-thp_cow-v1-0-005377483738@tencent.com>

From: Luka Bai <lukabai@tencent.com>

For pmd mapped anonymous folios, we currently do not do COW for the
whole vma region, because we don't want to copy and unshare the full
PMD range on the first write fault.

That proposal holds for the most workloads, however, that also makes
the pmd entry split into 512 4K ptes in the child process after we
write on a part of the folio.

For example, if process A and B share a pmd sized folio, if B does
writing on a small region, its pmd mapping will be split into 511
4K ptes which still point to the original pmd sized folio, and 1 4K
pte pointing to the new 4K page.

This is quite good for memory utilization, but it also make the tlb gain
caused by pmd entry suddenly "vanish" after a simple write, which
causes a observable performance decrease in some workloads. And also,
it adds some "uncertainty" to the THP since it does splitting
transparently in the COW scenorio which sometimes can cause trouble
to ones that need stable hugepages.

This patch adds support for pmd sized COW of anonymous page with
switch controlling. The reason we add switch is that for some scenorio,
the performance matters more, but for other workloads maybe the memory
waste is more unbearable. So we can use the THP setup to control this
configuration, either on the vma level or the global level.

The patch is relatively simple, we add function wp_huge_pmd_page_copy
to do the hugepage copy on write part, and do the allocation, accouting
and cache flushing just like in 4K path. We use the newly reconstructed
map_anon_folio_pmd_pf to do the mapping since it can properly support
FAULT_FLAG_UNSHARE right now.

We remove the ref checking in do_huge_pmd_wp_page, since we have
supported copying the pmd folio right now, we'll check the refcount
in the following folio_ref_count to make sure if the folio can be
exclusively used. If not, we can always do copy on write for this
folio just like in do_wp_page when THP COW is enabled.

Signed-off-by: Luka Bai <lukabai@tencent.com>
---
 mm/huge_memory.c | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 116 insertions(+), 9 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 1e661b411b2e..a05a4456e5a2 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -40,6 +40,7 @@
 #include <linux/pgalloc.h>
 #include <linux/pgalloc_tag.h>
 #include <linux/pagewalk.h>
+#include <linux/delayacct.h>
 
 #include <asm/tlb.h>
 #include "internal.h"
@@ -2196,6 +2197,94 @@ static vm_fault_t do_huge_zero_wp_pmd(struct vm_fault *vmf)
 	return ret;
 }
 
+static vm_fault_t wp_huge_pmd_page_copy(struct vm_fault *vmf, struct folio *old_folio)
+{
+	struct vm_area_struct *vma = vmf->vma;
+	struct mm_struct *mm = vma->vm_mm;
+	struct folio *new_folio = NULL;
+	struct page *new_page, *old_page;
+	unsigned long pmd_address = vmf->address & HPAGE_PMD_MASK;
+	struct mmu_notifier_range range;
+	vm_fault_t ret = 0;
+	int i;
+
+	delayacct_wpcopy_start();
+
+	old_page = folio_page(old_folio, 0);
+	ret = vmf_anon_prepare(vmf);
+	if (unlikely(ret)) {
+		if (ret != VM_FAULT_RETRY)
+			ret = VM_FAULT_FALLBACK;
+		goto out;
+	}
+
+	new_folio = vma_alloc_anon_folio_pmd(vma, vmf->address);
+	if (unlikely(!new_folio)) {
+		ret = VM_FAULT_FALLBACK;
+		goto out;
+	}
+
+	if (copy_user_large_folio(new_folio, old_folio,
+		pmd_address, vma)) {
+		ret = VM_FAULT_HWPOISON;
+		goto out;
+	}
+
+	new_page = folio_page(new_folio, 0);
+	for (i = 0; i < HPAGE_PMD_NR; i++)
+		kmsan_copy_page_meta(new_page + i, old_page + i);
+
+	__folio_mark_uptodate(new_folio);
+	mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, mm,
+				pmd_address, pmd_address + HPAGE_PMD_SIZE);
+	mmu_notifier_invalidate_range_start(&range);
+
+	spin_lock(vmf->ptl);
+	if (unlikely(!pmd_same(pmdp_get(vmf->pmd), vmf->orig_pmd))) {
+		update_mmu_cache_pmd(vma, pmd_address, vmf->pmd);
+		ret = 0;
+		goto out_unlock;
+	}
+
+	flush_cache_range(vma, pmd_address, pmd_address + HPAGE_PMD_SIZE);
+	/*
+	 * Clear the pmd entry and flush it first, before updating the
+	 * pmd with the new entry, to keep TLBs on different CPUs in
+	 * sync.
+	 */
+	(void)pmdp_huge_clear_flush(vma, pmd_address, vmf->pmd);
+	/*
+	 * We just temporarily decrement the mm_counter here, and it will be added back in
+	 * map_anon_folio_pmd_pf below.
+	 */
+	add_mm_counter(mm, MM_ANONPAGES, -HPAGE_PMD_NR);
+	map_anon_folio_pmd_pf(new_folio, vmf, true);
+	folio_remove_rmap_pmd(old_folio, old_page, vma);
+
+	spin_unlock(vmf->ptl);
+
+	mmu_notifier_invalidate_range_end(&range);
+	/* This put is for the folio_get() in the caller */
+	folio_put(old_folio);
+	free_swap_cache(old_folio);
+
+	/* This put is for decrementing refcount after we switch page table mapping */
+	folio_put(old_folio);
+
+	delayacct_wpcopy_end();
+	return 0;
+out_unlock:
+	spin_unlock(vmf->ptl);
+	mmu_notifier_invalidate_range_end(&range);
+out:
+	folio_put(old_folio);
+	if (new_folio)
+		folio_put(new_folio);
+
+	delayacct_wpcopy_end();
+	return ret;
+}
+
 vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf)
 {
 	const bool unshare = vmf->flags & FAULT_FLAG_UNSHARE;
@@ -2204,12 +2293,13 @@ vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf)
 	struct page *page;
 	unsigned long haddr = vmf->address & HPAGE_PMD_MASK;
 	pmd_t orig_pmd = vmf->orig_pmd;
+	vm_fault_t ret;
 
 	vmf->ptl = pmd_lockptr(vma->vm_mm, vmf->pmd);
 	VM_BUG_ON_VMA(!vma->anon_vma, vma);
 
 	if (is_huge_zero_pmd(orig_pmd)) {
-		vm_fault_t ret = do_huge_zero_wp_pmd(vmf);
+		ret = do_huge_zero_wp_pmd(vmf);
 
 		if (!(ret & VM_FAULT_FALLBACK))
 			return ret;
@@ -2253,14 +2343,6 @@ vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf)
 		goto reuse;
 	}
 
-	/*
-	 * See do_wp_page(): we can only reuse the folio exclusively if
-	 * there are no additional references. Note that we always drain
-	 * the LRU cache immediately after adding a THP.
-	 */
-	if (folio_ref_count(folio) >
-			1 + folio_test_swapcache(folio) * folio_nr_pages(folio))
-		goto unlock_fallback;
 	if (folio_test_swapcache(folio))
 		folio_free_swap(folio);
 	if (folio_ref_count(folio) == 1) {
@@ -2282,6 +2364,31 @@ vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf)
 		return 0;
 	}
 
+	/*
+	 * Only do hugepage copy on write if the parameter setup supports it.
+	 */
+	if (!hugepage_cow_enabled(vma))
+		goto unlock_fallback;
+
+	/*
+	 * For vma without a vm_ops(anonymous vma), there should not be VM_SHARED or
+	 * VM_MAYSHARE types.
+	 */
+	VM_WARN_ON_ONCE_VMA(vma->vm_flags & (VM_SHARED | VM_MAYSHARE), vma);
+
+	folio_unlock(folio);
+	/*
+	 * Copy on write branch here.
+	 * We are about to unlock the ptl here, so we need to get folio before that
+	 * in case the folio gets freed in the meantime.
+	 */
+	folio_get(folio);
+	spin_unlock(vmf->ptl);
+	ret = wp_huge_pmd_page_copy(vmf, folio);
+	if (ret & VM_FAULT_FALLBACK)
+		goto fallback;
+	return ret;
+
 unlock_fallback:
 	folio_unlock(folio);
 	spin_unlock(vmf->ptl);

-- 
2.52.0


^ permalink raw reply related

* [PATCH 4/5] mm: enable map_anon_folio_pmd_nopf to handle unshare
From: Luka Bai @ 2026-05-01  5:55 UTC (permalink / raw)
  To: linux-mm
  Cc: Jonathan Corbet, Shuah Khan, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
	Arnd Bergmann, Kairui Song, linux-kernel, linux-arch, linux-doc,
	Luka Bai
In-Reply-To: <20260501-thp_cow-v1-0-005377483738@tencent.com>

From: Luka Bai <lukabai@tencent.com>

Function map_anon_folio_pmd_nopf was able to map new anonymous pages.
Like in function do_huge_pmd_anonymous_page, it handles all the mappings
and statistics correctly in one call. However, it doesn't support
FAULT_FLAG_UNSHARE.

Normally, FAULT_FLAG_UNSHARE was set when we just want to separate
multiple non-exclusive sharing apart, it follows the copy on write
process, since it also does the checking like whether we need to copy
memory, or just use the existing one, basically the same work like
what COW does. But it doesn't happen because of writing on a RO pte/pmd
which is actually permitted to be written to but simply for "unsharing".
Hence we need to copy the same permissive and other marker flags into
the copied new page table entry just like the old one when doing the
duplication, without making it writable. Now, map_anon_folio_pmd_nopf
only tries to make the new pmd writable that is not what unsharing wants.

We add unsharing support for map_anon_folio_pmd_nopf by passing the
vm_fault struct as a parameter and get the unsharing hint. If we are in
the unsharing procedure, then we just copy the soft_dirty and uffd_wp
flags into the new pmd instead of trying to make the new pmd writable.

Signed-off-by: Luka Bai <lukabai@tencent.com>
---
 include/linux/huge_mm.h |  5 ++---
 mm/huge_memory.c        | 34 +++++++++++++++++++++++-----------
 mm/khugepaged.c         |  8 +++++++-
 3 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index 3e5c6da3905b..61f0e614ca52 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -610,9 +610,8 @@ void split_huge_pmd_locked(struct vm_area_struct *vma, unsigned long address,
 			   pmd_t *pmd, bool freeze);
 bool unmap_huge_pmd_locked(struct vm_area_struct *vma, unsigned long addr,
 			   pmd_t *pmdp, struct folio *folio);
-void map_anon_folio_pmd_nopf(struct folio *folio, pmd_t *pmd,
-		struct vm_area_struct *vma, unsigned long haddr);
-
+void map_anon_folio_pmd_nopf(struct folio *folio, struct vm_fault *vmf,
+			   bool cow);
 #else /* CONFIG_TRANSPARENT_HUGEPAGE */
 
 static inline bool folio_test_pmd_mappable(struct folio *folio)
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index babca060feca..1e661b411b2e 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -1423,13 +1423,26 @@ static struct folio *vma_alloc_anon_folio_pmd(struct vm_area_struct *vma,
 	return folio;
 }
 
-void map_anon_folio_pmd_nopf(struct folio *folio, pmd_t *pmd,
-		struct vm_area_struct *vma, unsigned long haddr)
+void map_anon_folio_pmd_nopf(struct folio *folio, struct vm_fault *vmf,
+		bool cow)
 {
 	pmd_t entry;
+	struct vm_area_struct *vma = vmf->vma;
+	pmd_t *pmd = vmf->pmd;
+	pmd_t orig_pmd = vmf->orig_pmd;
+	unsigned long haddr = vmf->address & HPAGE_PMD_MASK;
+	const bool unshare = vmf->flags & FAULT_FLAG_UNSHARE;
 
 	entry = folio_mk_pmd(folio, vma->vm_page_prot);
-	entry = maybe_pmd_mkwrite(pmd_mkdirty(entry), vma);
+	if (unlikely(cow && unshare)) {
+		VM_WARN_ON(pmd_write(orig_pmd));
+		if (pmd_soft_dirty(orig_pmd))
+			entry = pmd_mksoft_dirty(entry);
+		if (pmd_uffd_wp(orig_pmd))
+			entry = pmd_mkuffd_wp(entry);
+	} else {
+		entry = maybe_pmd_mkwrite(pmd_mkdirty(entry), vma);
+	}
 	folio_add_new_anon_rmap(folio, vma, haddr, RMAP_EXCLUSIVE);
 	folio_add_lru_vma(folio, vma);
 	set_pmd_at(vma->vm_mm, haddr, pmd, entry);
@@ -1437,19 +1450,18 @@ void map_anon_folio_pmd_nopf(struct folio *folio, pmd_t *pmd,
 	deferred_split_folio(folio, false);
 }
 
-static void map_anon_folio_pmd_pf(struct folio *folio, pmd_t *pmd,
-		struct vm_area_struct *vma, unsigned long haddr)
+static void map_anon_folio_pmd_pf(struct folio *folio, struct vm_fault *vmf,
+		bool cow)
 {
-	map_anon_folio_pmd_nopf(folio, pmd, vma, haddr);
-	add_mm_counter(vma->vm_mm, MM_ANONPAGES, HPAGE_PMD_NR);
+	map_anon_folio_pmd_nopf(folio, vmf, cow);
+	add_mm_counter(vmf->vma->vm_mm, MM_ANONPAGES, HPAGE_PMD_NR);
 	count_vm_event(THP_FAULT_ALLOC);
 	count_mthp_stat(HPAGE_PMD_ORDER, MTHP_STAT_ANON_FAULT_ALLOC);
-	count_memcg_event_mm(vma->vm_mm, THP_FAULT_ALLOC);
+	count_memcg_event_mm(vmf->vma->vm_mm, THP_FAULT_ALLOC);
 }
 
 static vm_fault_t __do_huge_pmd_anonymous_page(struct vm_fault *vmf)
 {
-	unsigned long haddr = vmf->address & HPAGE_PMD_MASK;
 	struct vm_area_struct *vma = vmf->vma;
 	struct folio *folio;
 	pgtable_t pgtable;
@@ -1483,7 +1495,7 @@ static vm_fault_t __do_huge_pmd_anonymous_page(struct vm_fault *vmf)
 			return ret;
 		}
 		pgtable_trans_huge_deposit(vma->vm_mm, vmf->pmd, pgtable);
-		map_anon_folio_pmd_pf(folio, vmf->pmd, vma, haddr);
+		map_anon_folio_pmd_pf(folio, vmf, false);
 		mm_inc_nr_ptes(vma->vm_mm);
 		spin_unlock(vmf->ptl);
 	}
@@ -2174,7 +2186,7 @@ static vm_fault_t do_huge_zero_wp_pmd(struct vm_fault *vmf)
 	if (ret)
 		goto release;
 	(void)pmdp_huge_clear_flush(vma, haddr, vmf->pmd);
-	map_anon_folio_pmd_pf(folio, vmf->pmd, vma, haddr);
+	map_anon_folio_pmd_pf(folio, vmf, true);
 	goto unlock;
 release:
 	folio_put(folio);
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 7d48d4fbd5f3..18d309b69d30 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -1402,7 +1402,13 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
 	if (is_pmd_order(order)) { /* PMD collapse */
 		pgtable = pmd_pgtable(_pmd);
 		pgtable_trans_huge_deposit(mm, pmd, pgtable);
-		map_anon_folio_pmd_nopf(folio, pmd, vma, pmd_addr);
+		struct vm_fault vmf = {
+			.vma = vma,
+			.flags = 0,
+			.address = pmd_addr,
+			.orig_pmd = pmdp_get(pmd),
+		};
+		map_anon_folio_pmd_nopf(folio, &vmf, false);
 	} else { /* mTHP collapse */
 		map_anon_folio_pte_nopf(folio, pte, vma, start_addr, /*uffd_wp=*/ false);
 		smp_wmb(); /* make PTEs visible before PMD. See pmd_install() */

-- 
2.52.0


^ permalink raw reply related

* [PATCH 3/5] mm: add pmd level THP COW judgement helpers
From: Luka Bai @ 2026-05-01  5:55 UTC (permalink / raw)
  To: linux-mm
  Cc: Jonathan Corbet, Shuah Khan, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
	Arnd Bergmann, Kairui Song, linux-kernel, linux-arch, linux-doc,
	Luka Bai
In-Reply-To: <20260501-thp_cow-v1-0-005377483738@tencent.com>

From: Luka Bai <lukabai@tencent.com>

We add hugepage_cow_always and hugepage_cow_madvise as two convenient
helpers to decide whether we want to do THP COW under each specific
circumstance.

Also, we add a helper hugepage_cow_enabled to help us know the setup
more easily. THP COW is only opened when hugepage is globally enabled
or madvise enabled.

Signed-off-by: Luka Bai <lukabai@tencent.com>
---
 include/linux/huge_mm.h | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index 2a62f0f92f68..3e5c6da3905b 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -203,6 +203,38 @@ static inline bool hugepage_global_always(void)
 			(1<<TRANSPARENT_HUGEPAGE_FLAG);
 }
 
+static inline bool hugepage_cow_always(void)
+{
+	return transparent_hugepage_flags &
+			(1<<TRANSPARENT_HUGEPAGE_COW_FLAG);
+}
+
+static inline bool hugepage_cow_madvise(void)
+{
+	return transparent_hugepage_flags &
+			(1<<TRANSPARENT_HUGEPAGE_REQ_MADV_COW_FLAG);
+}
+
+static inline bool hugepage_cow_enabled(struct vm_area_struct *vma)
+{
+	vm_flags_t vm_flags = vma->vm_flags;
+
+	/* anonymous THP need to be enabled first */
+	if (!hugepage_global_always() &&
+		(!hugepage_global_enabled() || !(vm_flags & VM_HUGEPAGE)))
+		return false;
+
+	/* always enables all the THP COW */
+	if (hugepage_cow_always())
+		return true;
+
+	/* madvise enables THP cow only when vm_flags says so */
+	if (hugepage_cow_madvise() && (vm_flags & VM_THP_COW))
+		return true;
+
+	return false;
+}
+
 static inline int highest_order(unsigned long orders)
 {
 	return fls_long(orders) - 1;

-- 
2.52.0


^ permalink raw reply related

* [PATCH 2/5] mm: add pmd level THP COW parameter in sysfs
From: Luka Bai @ 2026-05-01  5:55 UTC (permalink / raw)
  To: linux-mm
  Cc: Jonathan Corbet, Shuah Khan, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
	Arnd Bergmann, Kairui Song, linux-kernel, linux-arch, linux-doc,
	Luka Bai
In-Reply-To: <20260501-thp_cow-v1-0-005377483738@tencent.com>

From: Luka Bai <lukabai@tencent.com>

We would like to use similar logic of huge anonymous page or huge shmem
pages for THP COW: to categorize the strategies into three types: always,
never, madvise. If setting up to always, then we always do THP COW for
all the existing THPs. If setting up to never, then we never do THP COW.
If setting up to madvise, then we follow the setup we introduced in last
commit to decide whether we do COW for each individual vma.

We add TRANSPARENT_HUGEPAGE_COW_FLAG and
TRANSPARENT_HUGEPAGE_REQ_MADV_COW_FLAG that are very similar to
the TRANSPARENT_HUGEPAGE_FLAG and TRANSPARENT_HUGEPAGE_REQ_MADV_FLAG
which are used to decide whether we do anonymous huge page fault when
it permits. And we add sysfs attribute thp_cow_attr as the interface
to choose from the three strategies we mentioned before.

Signed-off-by: Luka Bai <lukabai@tencent.com>
---
 .../testing/sysfs-kernel-mm-transparent-hugepage   |  1 +
 Documentation/admin-guide/mm/transhuge.rst         | 27 +++++++++++++++
 include/linux/huge_mm.h                            |  2 ++
 mm/huge_memory.c                                   | 39 ++++++++++++++++++++++
 4 files changed, 69 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-transparent-hugepage b/Documentation/ABI/testing/sysfs-kernel-mm-transparent-hugepage
index 7bfbb9cc2c11..43a1af13efe0 100644
--- a/Documentation/ABI/testing/sysfs-kernel-mm-transparent-hugepage
+++ b/Documentation/ABI/testing/sysfs-kernel-mm-transparent-hugepage
@@ -11,6 +11,7 @@ Description:
 			- khugepaged
 			- shmem_enabled
 			- use_zero_page
+			- thp_cow
 			- subdirectories of the form hugepages-<size>kB, where <size>
 			  is the page size of the hugepages supported by the kernel/CPU
 			  combination.
diff --git a/Documentation/admin-guide/mm/transhuge.rst b/Documentation/admin-guide/mm/transhuge.rst
index 0ef13c451ac8..0926651bad0d 100644
--- a/Documentation/admin-guide/mm/transhuge.rst
+++ b/Documentation/admin-guide/mm/transhuge.rst
@@ -226,6 +226,33 @@ to "always" or "madvise"), and it'll be automatically shutdown when
 all THP sizes are disabled (when both the per-size anon control and the
 top-level control are "never")
 
+Some workloads may want to do copy on write on the pmd size to acquire the
+tlb benifit when it tries to write on a shared anonymous pmd sized entry.
+They can do so by setting up the thp_cow control. The control is only enabled
+when the global THP controls are set to "always" or "madvise" for the
+specific memory region::
+
+::
+
+	echo always >/sys/kernel/mm/transparent_hugepage/thp_cow
+	echo madvise >/sys/kernel/mm/transparent_hugepage/thp_cow
+	echo never >/sys/kernel/mm/transparent_hugepage/thp_cow
+
+always
+	means that the writing process will always do copy on write on
+	the pmd size. If there is no pmd sized folio available, it will
+	fallback to the pte size.
+
+madvise
+	will do things like ``always`` but only for regions that have
+	used madvise(MADV_THP_COW).
+
+never
+	will not do copy on write on the pmd size no matter what setup
+	is done using madvise. When a process writes on a shared anonymous
+	pmd sized entry, it will just allocate a pte sized page and do copy
+	on write on the pte size.
+
 process THP controls
 --------------------
 
diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index a0ce8c0b81f5..2a62f0f92f68 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -57,6 +57,8 @@ enum transparent_hugepage_flag {
 	TRANSPARENT_HUGEPAGE_DEFRAG_REQ_MADV_FLAG,
 	TRANSPARENT_HUGEPAGE_DEFRAG_KHUGEPAGED_FLAG,
 	TRANSPARENT_HUGEPAGE_USE_ZERO_PAGE_FLAG,
+	TRANSPARENT_HUGEPAGE_COW_FLAG,
+	TRANSPARENT_HUGEPAGE_REQ_MADV_COW_FLAG,
 };
 
 struct kobject;
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 1f0d0b780943..babca060feca 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -531,6 +531,44 @@ static ssize_t split_underused_thp_store(struct kobject *kobj,
 static struct kobj_attribute split_underused_thp_attr = __ATTR(
 	shrink_underused, 0644, split_underused_thp_show, split_underused_thp_store);
 
+static ssize_t thp_cow_show(struct kobject *kobj,
+			    struct kobj_attribute *attr, char *buf)
+{
+	const char *output;
+
+	if (test_bit(TRANSPARENT_HUGEPAGE_COW_FLAG, &transparent_hugepage_flags))
+		output = "[always] madvise never";
+	else if (test_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_COW_FLAG,
+			  &transparent_hugepage_flags))
+		output = "always [madvise] never";
+	else
+		output = "always madvise [never]";
+
+	return sysfs_emit(buf, "%s\n", output);
+}
+
+static ssize_t thp_cow_store(struct kobject *kobj,
+			     struct kobj_attribute *attr,
+			     const char *buf, size_t count)
+{
+	ssize_t ret = count;
+
+	if (sysfs_streq(buf, "always")) {
+		clear_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_COW_FLAG, &transparent_hugepage_flags);
+		set_bit(TRANSPARENT_HUGEPAGE_COW_FLAG, &transparent_hugepage_flags);
+	} else if (sysfs_streq(buf, "madvise")) {
+		clear_bit(TRANSPARENT_HUGEPAGE_COW_FLAG, &transparent_hugepage_flags);
+		set_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_COW_FLAG, &transparent_hugepage_flags);
+	} else if (sysfs_streq(buf, "never")) {
+		clear_bit(TRANSPARENT_HUGEPAGE_COW_FLAG, &transparent_hugepage_flags);
+		clear_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_COW_FLAG, &transparent_hugepage_flags);
+	} else
+		ret = -EINVAL;
+
+	return ret;
+}
+static struct kobj_attribute thp_cow_attr = __ATTR_RW(thp_cow);
+
 static struct attribute *hugepage_attr[] = {
 	&enabled_attr.attr,
 	&defrag_attr.attr,
@@ -540,6 +578,7 @@ static struct attribute *hugepage_attr[] = {
 	&shmem_enabled_attr.attr,
 #endif
 	&split_underused_thp_attr.attr,
+	&thp_cow_attr.attr,
 	NULL,
 };
 

-- 
2.52.0


^ permalink raw reply related

* [PATCH 1/5] mm: add basic madvise helpers and branch for THP setup
From: Luka Bai @ 2026-05-01  5:55 UTC (permalink / raw)
  To: linux-mm
  Cc: Jonathan Corbet, Shuah Khan, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
	Arnd Bergmann, Kairui Song, linux-kernel, linux-arch, linux-doc,
	Luka Bai
In-Reply-To: <20260501-thp_cow-v1-0-005377483738@tencent.com>

From: Luka Bai <lukabai@tencent.com>

Transparent huge page is now properly working with most of the mm
framework, and well fused with the folio concept that can be
reclaimed or allocated with a large order. However, its deed is not
very "estimable". For example, a THP is easily split in many path like
partially mapped, swap out or fork + COW(for child processes).

In some cases, we may want it to have some concluded result. Since
some workloads expect a relatively "stable" THP, while others may want
to save memory more rather than the performance benifits.

This patch adds some basic helpers and branch in madvise path so that
we can add madvise choices on THP to conduct what we do on different
types of operations like COW or swap that may split THP, on the level
of vma.

We transfer the type of configuration using parameters of madvise,
analyze it and save the result in vma->vm_flags for later use.

Currently the only operation in the list is COW. It decides whether
we want to use hugepages for the child process when it writes a spot
on the shared anonymous pmd so that we can make sure the THP not
being split after writing. This patch only adds the basic setup
helpers, the real usage will be added in the later patches.

Signed-off-by: Luka Bai <lukabai@tencent.com>
---
 include/linux/huge_mm.h                |  6 ++++++
 include/linux/mm.h                     | 19 +++++++++++++++++++
 include/uapi/asm-generic/mman-common.h |  9 +++++++++
 mm/madvise.c                           | 25 +++++++++++++++++++++++++
 4 files changed, 59 insertions(+)

diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index 48496f09909b..a0ce8c0b81f5 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -6,6 +6,7 @@
 
 #include <linux/fs.h> /* only for vma_is_dax() */
 #include <linux/kobject.h>
+#include <uapi/asm-generic/mman-common.h>
 
 vm_fault_t do_huge_pmd_anonymous_page(struct vm_fault *vmf);
 int copy_huge_pmd(struct mm_struct *dst_mm, struct mm_struct *src_mm,
@@ -363,6 +364,11 @@ static inline bool thp_disabled_by_hw(void)
 	return transparent_hugepage_flags & (1 << TRANSPARENT_HUGEPAGE_UNSUPPORTED);
 }
 
+static inline bool madv_thp_cow(int behavior)
+{
+	return behavior & MADV_THP_COW;
+}
+
 unsigned long thp_get_unmapped_area(struct file *filp, unsigned long addr,
 		unsigned long len, unsigned long pgoff, unsigned long flags);
 unsigned long thp_get_unmapped_area_vmflags(struct file *filp, unsigned long addr,
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 1d76da6e0791..8a800819cfa2 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -391,6 +391,10 @@ enum {
 #else
 	DECLARE_VMA_BIT_ALIAS(STACK, GROWSDOWN),
 #endif
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+	DECLARE_VMA_BIT(THP_SETUP_1, 43),
+	DECLARE_VMA_BIT_ALIAS(THP_COW, THP_SETUP_1),
+#endif
 };
 #undef DECLARE_VMA_BIT
 #undef DECLARE_VMA_BIT_ALIAS
@@ -510,6 +514,9 @@ enum {
 #define VM_DROPPABLE		VM_NONE
 #define VMA_DROPPABLE		EMPTY_VMA_FLAGS
 #endif
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+#define VM_THP_COW	INIT_VM_FLAG(THP_COW)
+#endif
 
 /* Bits set in the VMA until the stack is in its final location */
 #define VM_STACK_INCOMPLETE_SETUP (VM_RAND_READ | VM_SEQ_READ | VM_STACK_EARLY)
@@ -4128,6 +4135,18 @@ extern int do_munmap(struct mm_struct *, unsigned long, size_t,
 		     struct list_head *uf);
 extern int do_madvise(struct mm_struct *mm, unsigned long start, size_t len_in, int behavior);
 
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+static inline bool madv_thp_behavior(int behavior)
+{
+	return behavior >= MADV_THP_SETUP_BASE && behavior < MADV_THP_SETUP_END;
+}
+#else
+static inline bool madv_thp_behavior(int behavior)
+{
+	return false;
+}
+#endif
+
 #ifdef CONFIG_MMU
 extern int __mm_populate(unsigned long addr, unsigned long len,
 			 int ignore_errors);
diff --git a/include/uapi/asm-generic/mman-common.h b/include/uapi/asm-generic/mman-common.h
index ef1c27fa3c57..1617ed374503 100644
--- a/include/uapi/asm-generic/mman-common.h
+++ b/include/uapi/asm-generic/mman-common.h
@@ -82,6 +82,15 @@
 #define MADV_GUARD_INSTALL 102		/* fatal signal on access to range */
 #define MADV_GUARD_REMOVE 103		/* unguard range */
 
+/* for THP setup */
+#define MADV_THP_SETUP_BASE 256
+enum {
+	MADV_THP_COW_BIT,
+	MADV_THP_SETUP_MAX_BIT,
+};
+#define MADV_THP_COW        (MADV_THP_SETUP_BASE + (1 << MADV_THP_COW_BIT))
+#define MADV_THP_SETUP_END	(MADV_THP_SETUP_BASE + (1 << MADV_THP_SETUP_MAX_BIT))
+
 /* compatibility flags */
 #define MAP_FILE	0
 
diff --git a/mm/madvise.c b/mm/madvise.c
index 69708e953cf5..5dbfc89682d7 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -1331,6 +1331,25 @@ static bool can_madvise_modify(struct madvise_behavior *madv_behavior)
 }
 #endif
 
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+static vm_flags_t madvise_thp_setup(struct madvise_behavior *madv_behavior)
+{
+	int thp_behavior = madv_behavior->behavior - MADV_THP_SETUP_BASE;
+	struct vm_area_struct *vma = madv_behavior->vma;
+	vm_flags_t new_flags = vma->vm_flags;
+
+	if (madv_thp_cow(thp_behavior))
+		new_flags |= VM_THP_COW;
+
+	return new_flags;
+}
+#else
+static vm_flags_t madvise_thp_setup(struct madvise_behavior *madv_behavior)
+{
+	return madv_behavior->vma->vm_flags;
+}
+#endif
+
 /*
  * Apply an madvise behavior to a region of a vma.  madvise_update_vma
  * will handle splitting a vm area into separate areas, each area with its own
@@ -1427,6 +1446,10 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)
 		break;
 	}
 
+	/* Handle THP behaviors */
+	if (madv_thp_behavior(behavior))
+		new_flags = madvise_thp_setup(madv_behavior);
+
 	/* This is a write operation.*/
 	VM_WARN_ON_ONCE(madv_behavior->lock_mode != MADVISE_MMAP_WRITE_LOCK);
 
@@ -1555,6 +1578,8 @@ madvise_behavior_valid(int behavior)
 		return true;
 
 	default:
+		if (madv_thp_behavior(behavior))
+			return true;
 		return false;
 	}
 }

-- 
2.52.0


^ permalink raw reply related

* [PATCH 0/5] mm: Support selecting doing direct COW for anonymous pmd entry
From: Luka Bai @ 2026-05-01  5:55 UTC (permalink / raw)
  To: linux-mm
  Cc: Jonathan Corbet, Shuah Khan, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
	Arnd Bergmann, Kairui Song, linux-kernel, linux-arch, linux-doc,
	Luka Bai

Copy on write support for anonymous pmd level THP is simple right now:
firstly we'll check whether the folio can be exclusively used by the
faulting process, if we can (when the ref of the folio is only 1 after
trying to free swapcache or the page flag AnonExclusive is setup) we'll
directly use it with few further handling. If we cannot, then we'll
split the pmd into 512 4K ptes, and do copy on write only for the
specific 4K page that we faulted on.

This logic is truly memory efficient since for most workloads we don't
want to allocate 2M new memory simply on a small write. However, it also
makes the original 2M page for the process suddenly splitted on a
write which will generate some performance thrashing. For example, if
process A and process B share an anonymous 2M pmd, if process B chooses
to do a writing, then its page table mapping will be changed from 1
pmd entry into 512 4K pte entries at once, so the tlb benifit will
suddenly just "vanish" for process B, which sometimes may cause a
observable performance degeneration. After that, we can only wait for
khugepaged to do the collapse for this area and merge the pmd back, which
is not easy to happen.

In addition to the problem above, this logic can also generate some
deficiency for THP itself. Currently THP is just a "best-effort" choice
with no "certainty". THP is easily splitted into multiple small pages
on common calling path like reclaiming, COW. A transparent splitting
can cause throughput fluctuation for some workloads. For these workloads,
we may want to give THP some "certainty" just like hugetlbfs, The effect
we want is: after some customized setup, if only the system has usable
folio, and the virtual memory alignment permits (or we setup to), we can
make sure we always use THP for it, the system will never split it except
the user wants to do so.

This patchset is about both two things above, firstly we add pmd level
THP COW support by revising the code in do_huge_pmd_wp_page, we added
switch for it because different workloads may need different resources,
for which memory saving may matter more rather than the 2M tlb gain.
The switch is very similar to the "enable" and "shmem_enable" in sysfs
path of transparent_hugepage. THP COW is only enabled when THP itself
is enabled globally or by madvise. And also, we add basic THP setup
helpers and branch in madvise path and add the THP COW choice to it for a
more fine-grained setup. Now the helpers only supports copy on write
related, but in the future we may be able to add more types of THP
configurations into it like swapping.

Patch Details:
========
* Patch 1 adds the basic THP setup helpers and branch in madvise path.
  Then we add THP COW parameter into it.
* Patch 2 adds the THP COW sysfs interface, the logic is very similar
  to enable and shmem_enable of THP.
* Patch 3 adds the helpers that will be used in the actual COW path
  to decide whether we choose to do pmd level THP COW.
* Patch 4 reconstructs map_anon_folio_pmd_nopf and map_anon_folio_pmd_pf
  to make it capable of doing mapping for copied new folio when the
  fault flag has FLAG_FAULT_UNSHARE.
* Patch 5 adds the actual support for pmd level THP COW, and uses all
  the switches and helpers in the above 4 patches to do the strategy
  control.

Thanks for reading. Comments and suggestions are very welcome!

Signed-off-by: Luka Bai <lukabai@tencent.com>
---
Luka Bai (5):
      mm: add basic madvise helpers and branch for THP setup
      mm: add pmd level THP COW parameter in sysfs
      mm: add pmd level THP COW judgement helpers
      mm: enable map_anon_folio_pmd_nopf to handle unshare
      mm: support choosing to do THP COW for anonymous pmd entry.

 .../testing/sysfs-kernel-mm-transparent-hugepage   |   1 +
 Documentation/admin-guide/mm/transhuge.rst         |  27 +++
 include/linux/huge_mm.h                            |  45 ++++-
 include/linux/mm.h                                 |  19 ++
 include/uapi/asm-generic/mman-common.h             |   9 +
 mm/huge_memory.c                                   | 198 ++++++++++++++++++---
 mm/khugepaged.c                                    |   8 +-
 mm/madvise.c                                       |  25 +++
 8 files changed, 308 insertions(+), 24 deletions(-)
---
base-commit: 41cd9e3d23b8fd9e6c3c0311e9cb0304442c6141
change-id: 20260501-thp_cow-94873ed30793

Best regards,
--  
Luka Bai <lukabai@tencent.com>


^ permalink raw reply

* [PATCH] docs: pt_BR: translate process/license-rules.rst
From: Daniel Pereira @ 2026-05-01  5:30 UTC (permalink / raw)
  To: Jonathan Corbet; +Cc: linux-doc, Daniel Pereira

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=y, Size: 23321 bytes --]

Translate the license-rules.rst document into Brazilian Portuguese.
This document provides guidelines on how licenses should be identified
and handled within the kernel source code.

Additionally, update the pt_BR/process/index.rst to include the new
translation in the documentation tree.

Signed-off-by: Daniel Pereira <danielmaraboo@gmail.com>
---
 Documentation/translations/pt_BR/index.rst    |   1 +
 .../pt_BR/process/license-rules.rst           | 484 ++++++++++++++++++
 2 files changed, 485 insertions(+)
 create mode 100644 Documentation/translations/pt_BR/process/license-rules.rst

diff --git a/Documentation/translations/pt_BR/index.rst b/Documentation/translations/pt_BR/index.rst
index 4a094d8b7..77c1a1cdc 100644
--- a/Documentation/translations/pt_BR/index.rst
+++ b/Documentation/translations/pt_BR/index.rst
@@ -67,6 +67,7 @@ kernel e sobre como ver seu trabalho integrado.
    :maxdepth: 1
 
    Introdução <process/1.Intro>
+   Regras de licenciamento <process/license-rules>
    Como começar <process/howto>
    Requisitos mínimos <process/changes>
    Conclave (Continuidade do projeto) <process/conclave>
diff --git a/Documentation/translations/pt_BR/process/license-rules.rst b/Documentation/translations/pt_BR/process/license-rules.rst
new file mode 100644
index 000000000..93ecc57f3
--- /dev/null
+++ b/Documentation/translations/pt_BR/process/license-rules.rst
@@ -0,0 +1,484 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Regras de licenciamento do kernel Linux
+=======================================
+
+O Kernel Linux é fornecido apenas sob os termos da licença GNU General Public
+License versão 2 (GPL-2.0), conforme estabelecido em LICENSES/preferred/GPL-2.0,
+com uma exceção explícita para syscalls descrita em
+LICENSES/exceptions/Linux-syscall-note, conforme descrito no arquivo COPYING.
+
+Este arquivo de documentação fornece uma descrição de como cada arquivo-fonte
+deve ser anotado para tornar sua licença clara e inequívoca. Ele não substitui
+a licença do Kernel.
+
+A licença descrita no arquivo COPYING aplica-se ao código-fonte do kernel como
+um todo, embora arquivos-fonte individuais possam ter uma licença diferente, a
+qual deve ser obrigatoriamente compatível com a GPL-2.0::
+
+    GPL-1.0+  :  GNU General Public License v1.0 ou posterior
+    GPL-2.0+  :  GNU General Public License v2.0 ou posterior
+    LGPL-2.0  :  GNU Library General Public License v2 apenas
+    LGPL-2.0+ :  GNU Library General Public License v2 ou posterior
+    LGPL-2.1  :  GNU Lesser General Public License v2.1 apenas
+    LGPL-2.1+ :  GNU Lesser General Public License v2.1 ou posterior
+
+Além disso, arquivos individuais podem ser fornecidos sob uma licença dupla
+(dual license), por exemplo, uma das variantes GPL compatíveis e,
+alternativamente, sob uma licença permissiva como BSD, MIT, etc.
+
+Os arquivos de cabeçalho da API do espaço do usuário (UAPI), que descrevem a
+interface dos programas do espaço do usuário com o kernel, são um caso especial.
+De acordo com a nota no arquivo COPYING do kernel, a interface de syscall é um
+limite claro, que não estende os requisitos da GPL a qualquer software que a
+utilize para se comunicar com o kernel. Como os cabeçalhos UAPI devem ser
+passíveis de inclusão em quaisquer arquivos-fonte que criem um executável
+executado no kernel Linux, a exceção deve ser documentada por uma expressão de
+licença especial.
+
+A forma comum de expressar a licença de um arquivo-fonte é adicionar o texto
+padrão (boilerplate) correspondente no comentário inicial do arquivo. Devido a
+variações de formatação, erros de digitação, etc., esses "textos padrão" são
+difíceis de validar por ferramentas usadas no contexto de conformidade de
+licença.
+
+Uma alternativa aos textos padrão é o uso de identificadores de licença
+Software Package Data Exchange (SPDX) em cada arquivo-fonte. Os identificadores
+de licença SPDX são abreviações precisas e analisáveis por máquina para a
+licença sob a qual o conteúdo do arquivo é contribuído. Os identificadores de
+licença SPDX são gerenciados pelo Grupo de Trabalho SPDX na Linux Foundation e
+foram acordados por parceiros em toda a indústria, fornecedores de ferramentas
+e equipes jurídicas. Para mais informações, consulte https://spdx.org/
+
+O kernel Linux exige o identificador SPDX preciso em todos os arquivos-fonte.
+Os identificadores válidos usados no kernel são explicados na seção
+`Identificadores de Licença`_ e foram obtidos da lista de licenças
+oficial do  SPDX em https://spdx.org/licenses/ junto com os textos das licenças.
+
+Sintaxe do identificador de licença
+-----------------------------------
+
+1. Posicionamento:
+
+   O identificador de licença SPDX em arquivos do kernel deve ser adicionado na
+   primeira linha possível do arquivo que possa conter um comentário. Para a
+   maioria dos arquivos, esta é a primeira linha, exceto para scripts que
+   requerem o '#!CAMINHO_PARA_INTERPRETADOR' na primeira linha. Para esses
+   scripts, o identificador de licença SPDX deve ser colocado na segunda linha.
+
+   A linha do identificador de licença pode então ser seguida por uma ou
+   múltiplas linhas de SPDX-FileCopyrightText, se desejado.
+
+|
+2. Estilo:
+
+   O identificador de licença SPDX é adicionado na forma de um comentário. O
+   estilo do comentário depende do tipo de arquivo::
+
+      Fonte C:    // SPDX-License-Identifier: <Expressão de Licença SPDX>
+      Cabeçalho C:/* SPDX-License-Identifier: <Expressão de Licença SPDX> */
+      ASM:        /* SPDX-License-Identifier: <Expressão de Licença SPDX> */
+      scripts:    # SPDX-License-Identifier: <Expressão de Licença SPDX>
+      .rst:       .. SPDX-License-Identifier: <Expressão de Licença SPDX>
+      .dts{i}:    // SPDX-License-Identifier: <Expressão de Licença SPDX>
+
+   Se uma ferramenta específica não conseguir lidar com o estilo de comentário
+   padrão, então deve ser utilizado o mecanismo de comentário apropriado que a
+   ferramenta aceite. Este é o motivo para ter o comentário no estilo ``/* */``
+   em arquivos de cabeçalho C. Foi observada uma quebra de build com arquivos
+   .lds gerados, onde o 'ld' falhou ao analisar o comentário C++. Isso já foi
+   corrigido, mas ainda existem ferramentas de assembler mais antigas que não
+   conseguem lidar com comentários no estilo C++.
+
+3. Sintaxe:
+
+   Uma <Expressão de Licença SPDX> pode ser um identificador SPDX simplificado
+   encontrado na Lista de Licenças SPDX, ou a combinação de dois desses
+   identificadores separados por "WITH", caso uma exceção de licença se aplique.
+   Quando múltiplas licenças são aplicáveis, a expressão utiliza as palavras-chave
+   "AND" ou "OR" para separar as sub-expressões, que devem ser delimitadas
+   por parênteses "(", ")".
+
+   Para licenças como [L]GPL, utiliza-se o sufixo "+" para indicar a opção
+   'ou posterior'::
+
+      // SPDX-License-Identifier: GPL-2.0+
+      // SPDX-License-Identifier: LGPL-2.1+
+
+   O termo "WITH" deve ser usado sempre que houver um modificador necessário
+   para a licença. Por exemplo, os arquivos UAPI do kernel Linux utilizam a
+   expressão::
+
+      // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+      // SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note
+
+   Outros exemplos de uso da cláusula "WITH" para exceções no kernel são::
+
+      // SPDX-License-Identifier: GPL-2.0 WITH mif-exception
+      // SPDX-License-Identifier: GPL-2.0+ WITH GCC-exception-2.0
+
+   As exceções só podem ser aplicadas a identificadores de licença específicos.
+   Os identificadores válidos estão listados nas tags do arquivo de texto de
+   cada exceção. Para detalhes, veja o ponto `Exceções`_ no capítulo
+   `Identificadores de Licença`_.
+
+   O termo "OR" deve ser usado se o arquivo possuir licenciamento duplo (dual
+   licensed) e apenas uma das licenças puder ser selecionada. Por exemplo,
+   alguns arquivos dtsi estão disponíveis sob licença dupla::
+
+      // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+   Exemplos de expressões para arquivos com licenciamento duplo no kernel::
+
+      // SPDX-License-Identifier: GPL-2.0 OR MIT
+      // SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+      // SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
+      // SPDX-License-Identifier: GPL-2.0 OR MPL-1.1
+      // SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) OR MIT
+      // SPDX-License-Identifier: GPL-1.0+ OR BSD-3-Clause OR OpenSSL
+
+   O termo "AND" deve ser usado se o arquivo possuir múltiplas licenças cujos
+   termos devem ser aplicados simultaneamente. Por exemplo, se um código herdado
+   de outro projeto foi incorporado ao kernel, mas os termos da licença original
+   ainda precisam ser respeitados::
+
+      // SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) AND MIT
+
+   Outro exemplo onde ambos os conjuntos de termos devem ser cumpridos é::
+
+      // SPDX-License-Identifier: GPL-1.0+ AND LGPL-2.1+
+
+Identificadores de Licença
+--------------------------
+
+As licenças atualmente em uso, bem como as licenças para código adicionado ao
+kernel, podem ser divididas em:
+
+4. _`Licenças preferenciais`:
+
+   Sempre que possível, estas licenças devem ser utilizadas, pois são conhecidas
+   por serem totalmente compatíveis e amplamente usadas. Estas licenças estão
+   disponíveis no diretório::
+
+      LICENSES/preferred/
+
+   na árvore de diretórios do código-fonte do kernel.
+
+   Os arquivos neste diretório contêm o texto completo da licença e as
+   `Metatags`_. Os nomes dos arquivos são idênticos ao identificador de licença
+   SPDX que deve ser utilizado para a licença nos arquivos-fonte.
+
+   Exemplos::
+
+      LICENSES/preferred/GPL-2.0
+
+   Contém o texto da licença GPL versão 2 e as metatags obrigatórias::
+
+      LICENSES/preferred/MIT
+
+   Contém o texto da licença MIT e as metatags obrigatórias.
+
+   _`Metatags`:
+
+   As seguintes metatags devem estar presentes em um arquivo de licença:
+
+   - Valid-License-Identifier:
+
+     Uma ou mais linhas que declaram quais Identificadores de Licença são válidos
+     dentro do projeto para referenciar este texto de licença específico.
+     Geralmente, trata-se de um único identificador válido, mas, por exemplo,
+     para licenças com as opções 'ou posterior' (or later), dois identificadores
+     são válidos.
+
+   - SPDX-URL:
+
+     A URL da página SPDX que contém informações adicionais relacionadas à licença.
+
+   - Usage-Guidance:
+
+     Texto livre para conselhos de uso. O texto deve incluir exemplos corretos
+     para os identificadores de licença SPDX, conforme eles devem ser colocados
+     nos arquivos-fonte de acordo com as diretrizes de
+     `Sintaxe do identificador de licença`_.
+
+   - License-Text:
+
+     Todo o texto após esta tag é tratado como o texto original da licença.
+
+   Exemplos de formato de arquivo::
+
+      Valid-License-Identifier: GPL-2.0
+      Valid-License-Identifier: GPL-2.0+
+      SPDX-URL: https://spdx.org/licenses/GPL-2.0.html
+      Usage-Guide:
+        Para usar esta licença no código-fonte, coloque um dos seguintes pares
+        SPDX tag/valor em um comentário, de acordo com as diretrizes de
+        posicionamento na documentação das regras de licenciamento.
+        Para 'GNU General Public License (GPL) version 2 only', use:
+          SPDX-License-Identifier: GPL-2.0
+        Para 'GNU General Public License (GPL) version 2 or any later version', use:
+          SPDX-License-Identifier: GPL-2.0+
+      License-Text:
+        Texto completo da licença
+
+   ::
+
+      Valid-License-Identifier: MIT
+      SPDX-URL: https://spdx.org/licenses/MIT.html
+      Usage-Guide:
+        Para usar esta licença no código-fonte, coloque o seguinte par SPDX
+        tag/valor em um comentário, de acordo com as diretrizes de
+        posicionamento na documentação das regras de licenciamento.
+          SPDX-License-Identifier: MIT
+      License-Text:
+        Texto completo da licença
+
+5. Licenças obsoletas:
+
+   Estas licenças devem ser utilizadas apenas para código já existente ou para
+   a importação de código de outros projetos. Estas licenças estão disponíveis
+   no diretório::
+
+      LICENSES/deprecated/
+
+   na árvore de fontes do kernel.
+
+   Os arquivos neste diretório contêm o texto completo da licença e as
+   `Metatags`_. Os nomes dos arquivos são idênticos ao identificador de
+   licença SPDX que deve ser utilizado para a licença nos arquivos fonte.
+
+   Exemplos::
+
+      LICENSES/deprecated/ISC
+
+   Contém o texto da licença *Internet Systems Consortium* e as metatags
+   necessárias::
+
+      LICENSES/deprecated/GPL-1.0
+
+   Contém o texto da licença GPL versão 1 e as metatags necessárias.
+
+   Metatags:
+
+   Os requisitos de metatags para "outras" licenças são idênticos aos
+   requisitos das `Licenças preferenciais`_.
+
+   Exemplo de formato de arquivo::
+
+      Valid-License-Identifier: ISC
+      SPDX-URL: https://spdx.org/licenses/ISC.html
+      Usage-Guide:
+        O uso desta licença no kernel para novos códigos é desencorajado
+        e deve ser utilizada exclusivamente para importar código de um
+        projeto já existente.
+        Para usar esta licença no código-fonte, coloque o seguinte par
+        tag/valor SPDX em um comentário, seguindo as diretrizes de
+        posicionamento na documentação das regras de licenciamento.
+          SPDX-License-Identifier: ISC
+      License-Text:
+        Texto completo da licença
+
+6. Apenas Licenciamento Duplo
+
+   Estas licenças devem ser usadas apenas para o licenciamento duplo de código
+   com outra licença, além de uma licença preferencial. Estas licenças estão
+   disponíveis no diretório::
+
+      LICENSES/dual/
+
+   No código-fonte do kernel.
+
+   Os arquivos neste diretório contêm o texto completo da licença e as
+   `Metatags`_. Os nomes dos arquivos são idênticos ao identificador de licença
+   SPDX que deve ser usado para a licença nos arquivos fonte.
+
+   Exemplos::
+
+      LICENSES/dual/MPL-1.1
+
+   Contém o texto da licença Mozilla Public License versão 1.1 e as metatags
+   necessárias::
+
+      LICENSES/dual/Apache-2.0
+
+   Contém o texto da licença Apache License versão 2.0 e as metatags
+   necessárias.
+
+   Metatags:
+
+   Os requisitos de metatags para 'outras' licenças são idênticos aos
+   requisitos das `Licenças preferenciais`_.
+
+   Exemplo de formato de arquivo::
+
+      Valid-License-Identifier: MPL-1.1
+      SPDX-URL: https://spdx.org/licenses/MPL-1.1.html
+      Usage-Guide:
+        NÃO use. A licença MPL-1.1 não é compatível com a GPL2. Ela só pode ser
+        usada para arquivos com licenciamento duplo onde a outra licença seja
+        compatível com a GPL2.
+        Se você acabar utilizando-a, ela DEVE ser usada em conjunto com uma
+        licença compatível com a GPL2 utilizando "OR".
+        Para usar a Mozilla Public License versão 1.1, coloque o seguinte par
+        tag/valor SPDX em um comentário, de acordo com as diretrizes de
+        posicionamento na documentação das regras de licenciamento:
+      SPDX-License-Identifier: MPL-1.1
+      License-Text:
+        Texto completo da licença
+
+|
+
+7. _`Exceções`:
+
+Algumas licenças podem ser alteradas com exceções que concedem certos direitos
+   que a licença original não concede. Estas exceções estão disponíveis no
+   diretório::
+
+      LICENSES/exceptions/
+
+   no código-fonte do kernel. Os arquivos neste diretório contêm o texto completo
+   da exceção e as `Metatags de Exceção`_ necessárias.
+
+   Exemplos::
+
+      LICENSES/exceptions/Linux-syscall-note
+
+   Contém a exceção de syscall do Linux, conforme documentado no arquivo COPYING
+   do kernel Linux, que é usada para arquivos de cabeçalho UAPI.
+   ex: /\* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note \*/::
+
+      LICENSES/exceptions/GCC-exception-2.0
+
+   Contém a 'exceção de vinculação' do GCC, que permite
+   vincular qualquer binário, independente de sua licença, à versão compilada
+   de um arquivo marcado com esta exceção. Isso é necessário para criar
+   executáveis funcionais a partir de código-fonte que não seja compatível
+   com a GPL.
+
+   _`Metatags de Exceção`:
+
+   As seguintes meta tags devem estar disponíveis em um arquivo de exceção:
+
+   - SPDX-Exception-Identifier:
+
+     Um identificador de exceção que pode ser usado com identificadores de
+     licença SPDX.
+
+   - SPDX-URL:
+
+     A URL da página SPDX que contém informações adicionais relacionadas
+     à exceção.
+
+   - SPDX-Licenses:
+
+     Uma lista separada por vírgulas de identificadores de licença SPDX para os
+     quais a exceção pode ser usada.
+
+   - Usage-Guidance:
+
+     Texto de formato livre para conselhos de uso. O texto deve ser seguido por
+     exemplos corretos para os identificadores de licença SPDX, conforme devem
+     ser colocados nos arquivos fonte de acordo com as diretrizes de
+     `Sintaxe do identificador de licença`_.
+
+   - Exception-Text:
+
+     Todo o texto após esta tag é tratado como o texto original da exceção.
+
+   Exemplos de formato de arquivo::
+
+      SPDX-Exception-Identifier: Linux-syscall-note
+      SPDX-URL: https://spdx.org/licenses/Linux-syscall-note.html
+      SPDX-Licenses: GPL-2.0, GPL-2.0+, GPL-1.0+, LGPL-2.0, LGPL-2.0+, LGPL-2.1, LGPL-2.1+
+      Usage-Guidance:
+        Esta exceção é usada em conjunto com uma das SPDX-Licenses acima para
+        marcar arquivos de cabeçalho de API do espaço do usuário (uapi), para que
+        possam ser incluídos em código de aplicativo de espaço do usuário que não
+        esteja em conformidade com a GPL.
+        Para usar esta exceção, adicione-a com a palavra-chave WITH a um dos
+        identificadores na tag SPDX-Licenses:
+          SPDX-License-Identifier: <SPDX-License> WITH Linux-syscall-note
+      Exception-Text:
+        Texto completo da exceção
+
+Exemplos de formato de arquivo::
+
+      SPDX-Exception-Identifier: GCC-exception-2.0
+      SPDX-URL: https://spdx.org/licenses/GCC-exception-2.0.html
+      SPDX-Licenses: GPL-2.0, GPL-2.0+
+      Usage-Guidance:
+        A "GCC Runtime Library exception 2.0" é usada em conjunto com uma das
+        SPDX-Licenses acima para código importado da biblioteca de tempo de
+        execução (runtime) do GCC.
+        Para usar esta exceção, adicione-a com a palavra-chave WITH a um dos
+        identificadores na tag SPDX-Licenses:
+          SPDX-License-Identifier: <SPDX-License> WITH GCC-exception-2.0
+      Exception-Text:
+        Texto completo da exceção
+
+Todos os identificadores de licença e exceções SPDX devem ter um arquivo
+correspondente nos subdiretórios LICENSES. Isso é necessário para permitir a
+verificação por ferramentas (ex: checkpatch.pl) e para que as licenças estejam
+prontas para leitura e extração diretamente da fonte, o que é recomendado por
+várias organizações de FOSS (Software Livre e de Código Aberto), como a
+`iniciativa REUSE da FSFE <https://reuse.software/>`_.
+
+_`MODULE_LICENSE`
+-----------------
+
+   Módulos carregáveis do kernel também exigem uma tag MODULE_LICENSE(). Esta tag
+   não substitui as informações adequadas de licença do código-fonte
+   (SPDX-License-Identifier), nem é de forma alguma relevante para expressar ou
+   determinar a licença exata sob a qual o código-fonte do módulo é fornecido.
+
+   O único propósito desta tag é fornecer informações suficientes ao carregador
+   de módulos do kernel e às ferramentas de espaço do usuário sobre o módulo ser
+   software livre ou proprietário.
+
+   As strings de licença válidas para MODULE_LICENSE() são::
+
+      ============================= =============================================
+      "GPL"                         O módulo está licenciado sob a GPL versão 2.
+                                    Isso não expressa nenhuma distinção entre
+                                    GPL-2.0-only ou GPL-2.0-or-later. A informação
+                                    exata da licença só pode ser determinada por
+                                    meio das informações de licença nos arquivos
+                                    fonte correspondentes.
+
+      "GPL v2"                      O mesmo que "GPL". Existe por razões
+                                    históricas.
+
+      "GPL and additional rights"   Variante histórica para expressar que o fonte
+                                    do módulo possui licenciamento duplo sob uma
+                                    variante da GPL v2 e a licença MIT. Por favor,
+                                    não use em códigos novos.
+
+      "Dual MIT/GPL"                A maneira correta de expressar que o módulo
+                                    possui licenciamento duplo sob uma escolha de
+                                    variante GPL v2 ou licença MIT.
+
+      "Dual BSD/GPL"                O módulo possui licenciamento duplo sob uma
+                                    escolha de variante GPL v2 ou licença BSD. A
+                                    variante exata da licença BSD só pode ser
+                                    determinada por meio das informações de
+                                    licença nos arquivos fonte correspondentes.
+
+      "Dual MPL/GPL"                O módulo possui licenciamento duplo sob uma
+                                    escolha de variante GPL v2 ou Mozilla Public
+                                    License (MPL). A variante exata da licença
+                                    MPL só pode ser determinada por meio das
+                                    informações de licença nos arquivos fonte
+                                    correspondentes.
+
+      "Proprietary"                 O módulo está sob uma licença proprietária.
+                                    "Proprietary" deve ser entendido apenas como
+                                    "A licença não é compatível com GPLv2". Esta
+                                    string é exclusiva para módulos de terceiros
+                                    não compatíveis com GPL2 e não pode ser usada
+                                    para módulos que tenham seu código-fonte na
+                                    árvore do kernel. Módulos marcados dessa forma
+                                    contaminam (tainting) o kernel com a flag 'P'
+                                    quando carregados, e o carregador de módulos
+                                    recusa-se a vincular tais módulos a símbolos
+                                    exportados com EXPORT_SYMBOL_GPL().
+      ============================= =============================================
\ No newline at end of file
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1] docs: housekeeping: Fix struct member access in code example
From: Costa Shulyupin @ 2026-05-01  4:38 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Ryan Cheevers, Costa Shulyupin,
	Waiman Long, Frederic Weisbecker, linux-doc, linux-kernel

No such array housekeeping_cpumasks

Fix to housekeeping.cpumasks.

Signed-off-by: Costa Shulyupin <costa.shul@redhat.com>
---
 Documentation/core-api/housekeeping.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/core-api/housekeeping.rst b/Documentation/core-api/housekeeping.rst
index 92c6e53cea75..ccb0a88b9cb3 100644
--- a/Documentation/core-api/housekeeping.rst
+++ b/Documentation/core-api/housekeeping.rst
@@ -99,7 +99,7 @@ the same RCU read side critical section.
 A typical layout example would look like this on the update side
 (``housekeeping_update()``)::
 
-	rcu_assign_pointer(housekeeping_cpumasks[type], trial);
+	rcu_assign_pointer(housekeeping.cpumasks[type], trial);
 	synchronize_rcu();
 	flush_workqueue(example_workqueue);
 
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH 00/11] mm/damon: introduce DAMOS failed region quota charge ratio
From: SeongJae Park @ 2026-05-01  1:56 UTC (permalink / raw)
  To: SeongJae Park
  Cc: Andrew Morton, Liam R. Howlett, Brendan Higgins, David Gow,
	David Hildenbrand, Jonathan Corbet, Lorenzo Stoakes, Michal Hocko,
	Mike Rapoport, Shuah Khan, Shuah Khan, Suren Baghdasaryan,
	Vlastimil Babka, damon, kunit-dev, linux-doc, linux-kernel,
	linux-kselftest, linux-mm
In-Reply-To: <20260428152424.125760-1-sj@kernel.org>

On Tue, 28 Apr 2026 08:24:24 -0700 SeongJae Park <sj@kernel.org> wrote:

> Hello Andrew,
> 
> On Tue, 28 Apr 2026 07:48:37 -0700 Andrew Morton <akpm@linux-foundation.org> wrote:
> 
> > On Mon, 27 Apr 2026 18:33:49 -0700 SeongJae Park <sj@kernel.org> wrote:
> > 
> > > TL; DR: Let users set different DAMOS quota charge ratios for DAMOS
> > > action failed regions, for deterministic and consistent DAMOS action
> > > progress.
> > 
> > Add, thanks.
> > 
> > As mentioned provately, Sashiko claims to have found things which it
> > didn't see in the RFC.
> > 
> > 	https://sashiko.dev/#/patchset/20260428013402.115171-1-sj@kernel.org
> 
> TL; DR: I find no blocker for this patch series from the Sashiko reviews.
> 
> Now sashiko replies its reviews for DAMON patches to authors and
> damon@lists.linux.dev.  So I replied [1,2,3] my review of the reviews to those
> on damon@lists.linux.dev mailing list.  As I mentioned on the TL;DR, I find no
> blocker for this series.
> 
> And I think you didn't see those because those are sent to only authors and
> damon@lists.linux.dev.
> 
> I nowadays reply-all to original recipients only if Sashiko found a blocker.  I
> will also add short notice for non-RFC patches if Sashiko found zero issue.

... Now I think the short notice is only redundant and silly, as the full
review is available on damon@ list and the one who primarily interested in
(Andrew) understands that.  I feel like the short-notice reply-all only
increase unnecessary traffic and my redundant typing.  I will not do the short
notice broadcasting, unless someone makes a diffeernt voice.


Thanks,
SJ

[...]

^ permalink raw reply

* [PATCH 3/3] selftests/mm: Add userfaultfd test for HMM unlockable path
From: Stanislav Kinsburskii @ 2026-05-01  1:20 UTC (permalink / raw)
  To: kys, Liam.Howlett, akpm, akpm, david, decui, haiyangz, jgg,
	corbet, leon, longli, ljs, mhocko, rppt, shuah, skhan, surenb,
	vbabka, wei.liu
  Cc: linux-doc, linux-hyperv, linux-kernel, linux-kernel,
	linux-kselftest, linux-mm
In-Reply-To: <177759835313.221039.2807391868456411507.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>

Add a selftest that exercises hmm_range_fault_unlockable() with a
userfaultfd-backed mapping. The test:

1. Creates an anonymous mmap region
2. Registers it with userfaultfd (UFFDIO_REGISTER_MODE_MISSING)
3. Spawns a handler thread that responds to page faults by filling
   pages with a known pattern (0xAB) via UFFDIO_COPY
4. Issues HMM_DMIRROR_READ_UNLOCKABLE to the test_hmm driver, which
   calls hmm_range_fault_unlockable() internally
5. Verifies the device read back the data provided by the userfaultfd
   handler

This requires changes to the test_hmm kernel module:
- New dmirror_range_fault_unlockable() that uses the new HMM API
- New dmirror_fault_unlockable() and dmirror_read_unlockable() wrappers
- New HMM_DMIRROR_READ_UNLOCKABLE ioctl (0x09)

Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
---
 lib/test_hmm.c                         |  122 +++++++++++++++++++++++++++++
 lib/test_hmm_uapi.h                    |    1 
 tools/testing/selftests/mm/hmm-tests.c |  133 ++++++++++++++++++++++++++++++++
 3 files changed, 256 insertions(+)

diff --git a/lib/test_hmm.c b/lib/test_hmm.c
index 0964d53365e61..20b14e279a8bd 100644
--- a/lib/test_hmm.c
+++ b/lib/test_hmm.c
@@ -327,6 +327,84 @@ static int dmirror_range_fault(struct dmirror *dmirror,
 	return ret;
 }
 
+static int dmirror_range_fault_unlockable(struct dmirror *dmirror,
+					  struct hmm_range *range)
+{
+	struct mm_struct *mm = dmirror->notifier.mm;
+	unsigned long timeout =
+		jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
+	int locked;
+	int ret;
+
+	while (true) {
+		if (time_after(jiffies, timeout)) {
+			ret = -EBUSY;
+			goto out;
+		}
+
+		range->notifier_seq = mmu_interval_read_begin(range->notifier);
+		locked = 1;
+		mmap_read_lock(mm);
+		ret = hmm_range_fault_unlockable(range, &locked);
+		if (locked)
+			mmap_read_unlock(mm);
+		if (ret) {
+			if (ret == -EBUSY)
+				continue;
+			goto out;
+		}
+		if (!locked)
+			continue;
+
+		mutex_lock(&dmirror->mutex);
+		if (mmu_interval_read_retry(range->notifier,
+					    range->notifier_seq)) {
+			mutex_unlock(&dmirror->mutex);
+			continue;
+		}
+		break;
+	}
+
+	ret = dmirror_do_fault(dmirror, range);
+
+	mutex_unlock(&dmirror->mutex);
+out:
+	return ret;
+}
+
+static int dmirror_fault_unlockable(struct dmirror *dmirror,
+				    unsigned long start,
+				    unsigned long end, bool write)
+{
+	struct mm_struct *mm = dmirror->notifier.mm;
+	unsigned long addr;
+	unsigned long pfns[32];
+	struct hmm_range range = {
+		.notifier = &dmirror->notifier,
+		.hmm_pfns = pfns,
+		.pfn_flags_mask = 0,
+		.default_flags =
+			HMM_PFN_REQ_FAULT | (write ? HMM_PFN_REQ_WRITE : 0),
+		.dev_private_owner = dmirror->mdevice,
+	};
+	int ret = 0;
+
+	if (!mmget_not_zero(mm))
+		return 0;
+
+	for (addr = start; addr < end; addr = range.end) {
+		range.start = addr;
+		range.end = min(addr + (ARRAY_SIZE(pfns) << PAGE_SHIFT), end);
+
+		ret = dmirror_range_fault_unlockable(dmirror, &range);
+		if (ret)
+			break;
+	}
+
+	mmput(mm);
+	return ret;
+}
+
 static int dmirror_fault(struct dmirror *dmirror, unsigned long start,
 			 unsigned long end, bool write)
 {
@@ -426,6 +504,47 @@ static int dmirror_read(struct dmirror *dmirror, struct hmm_dmirror_cmd *cmd)
 	return ret;
 }
 
+static int dmirror_read_unlockable(struct dmirror *dmirror,
+				   struct hmm_dmirror_cmd *cmd)
+{
+	struct dmirror_bounce bounce;
+	unsigned long start, end;
+	unsigned long size = cmd->npages << PAGE_SHIFT;
+	int ret;
+
+	start = cmd->addr;
+	end = start + size;
+	if (end < start)
+		return -EINVAL;
+
+	ret = dmirror_bounce_init(&bounce, start, size);
+	if (ret)
+		return ret;
+
+	while (1) {
+		mutex_lock(&dmirror->mutex);
+		ret = dmirror_do_read(dmirror, start, end, &bounce);
+		mutex_unlock(&dmirror->mutex);
+		if (ret != -ENOENT)
+			break;
+
+		start = cmd->addr + (bounce.cpages << PAGE_SHIFT);
+		ret = dmirror_fault_unlockable(dmirror, start, end, false);
+		if (ret)
+			break;
+		cmd->faults++;
+	}
+
+	if (ret == 0) {
+		if (copy_to_user(u64_to_user_ptr(cmd->ptr), bounce.ptr,
+				 bounce.size))
+			ret = -EFAULT;
+	}
+	cmd->cpages = bounce.cpages;
+	dmirror_bounce_fini(&bounce);
+	return ret;
+}
+
 static int dmirror_do_write(struct dmirror *dmirror, unsigned long start,
 			    unsigned long end, struct dmirror_bounce *bounce)
 {
@@ -1537,6 +1656,9 @@ static long dmirror_fops_unlocked_ioctl(struct file *filp,
 		dmirror->flags = cmd.npages;
 		ret = 0;
 		break;
+	case HMM_DMIRROR_READ_UNLOCKABLE:
+		ret = dmirror_read_unlockable(dmirror, &cmd);
+		break;
 
 	default:
 		return -EINVAL;
diff --git a/lib/test_hmm_uapi.h b/lib/test_hmm_uapi.h
index f94c6d4573382..076df6df92275 100644
--- a/lib/test_hmm_uapi.h
+++ b/lib/test_hmm_uapi.h
@@ -38,6 +38,7 @@ struct hmm_dmirror_cmd {
 #define HMM_DMIRROR_CHECK_EXCLUSIVE	_IOWR('H', 0x06, struct hmm_dmirror_cmd)
 #define HMM_DMIRROR_RELEASE		_IOWR('H', 0x07, struct hmm_dmirror_cmd)
 #define HMM_DMIRROR_FLAGS		_IOWR('H', 0x08, struct hmm_dmirror_cmd)
+#define HMM_DMIRROR_READ_UNLOCKABLE	_IOWR('H', 0x09, struct hmm_dmirror_cmd)
 
 #define HMM_DMIRROR_FLAG_FAIL_ALLOC	(1ULL << 0)
 
diff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c
index e8328c89d855e..e7bf061747edd 100644
--- a/tools/testing/selftests/mm/hmm-tests.c
+++ b/tools/testing/selftests/mm/hmm-tests.c
@@ -26,6 +26,9 @@
 #include <sys/mman.h>
 #include <sys/ioctl.h>
 #include <sys/time.h>
+#include <sys/syscall.h>
+#include <linux/userfaultfd.h>
+#include <poll.h>
 
 
 /*
@@ -2852,4 +2855,134 @@ TEST_F_TIMEOUT(hmm, benchmark_thp_migration, 120)
 					&thp_results, &regular_results);
 	}
 }
+
+/*
+ * Test that HMM can fault in pages backed by userfaultfd using the
+ * hmm_range_fault_unlockable() path. This exercises the lock-drop retry
+ * logic in the HMM framework.
+ */
+struct uffd_thread_args {
+	int uffd;
+	void *page_buffer;
+	unsigned long page_size;
+};
+
+static void *uffd_handler_thread(void *arg)
+{
+	struct uffd_thread_args *args = arg;
+	struct uffd_msg msg;
+	struct uffdio_copy copy;
+	struct pollfd pollfd;
+	int ret;
+
+	pollfd.fd = args->uffd;
+	pollfd.events = POLLIN;
+
+	while (1) {
+		ret = poll(&pollfd, 1, 5000);
+		if (ret <= 0)
+			break;
+
+		ret = read(args->uffd, &msg, sizeof(msg));
+		if (ret != sizeof(msg))
+			break;
+
+		if (msg.event != UFFD_EVENT_PAGEFAULT)
+			break;
+
+		/* Fill the page with a known pattern */
+		memset(args->page_buffer, 0xAB, args->page_size);
+
+		copy.dst = msg.arg.pagefault.address & ~(args->page_size - 1);
+		copy.src = (unsigned long)args->page_buffer;
+		copy.len = args->page_size;
+		copy.mode = 0;
+		copy.copy = 0;
+
+		ret = ioctl(args->uffd, UFFDIO_COPY, &copy);
+		if (ret < 0)
+			break;
+	}
+
+	return NULL;
+}
+
+TEST_F(hmm, userfaultfd_read)
+{
+	struct hmm_buffer *buffer;
+	struct uffd_thread_args uffd_args;
+	unsigned long npages;
+	unsigned long size;
+	unsigned long i;
+	unsigned char *ptr;
+	pthread_t thread;
+	int uffd;
+	int ret;
+	struct uffdio_api api;
+	struct uffdio_register reg;
+
+	npages = 4;
+	size = npages << self->page_shift;
+
+	/* Create userfaultfd */
+	uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
+	if (uffd < 0)
+		SKIP(return, "userfaultfd not available");
+
+	api.api = UFFD_API;
+	api.features = 0;
+	ret = ioctl(uffd, UFFDIO_API, &api);
+	ASSERT_EQ(ret, 0);
+
+	buffer = malloc(sizeof(*buffer));
+	ASSERT_NE(buffer, NULL);
+
+	buffer->fd = -1;
+	buffer->size = size;
+	buffer->mirror = malloc(size);
+	ASSERT_NE(buffer->mirror, NULL);
+
+	/* Create anonymous mapping */
+	buffer->ptr = mmap(NULL, size,
+			   PROT_READ | PROT_WRITE,
+			   MAP_PRIVATE | MAP_ANONYMOUS,
+			   -1, 0);
+	ASSERT_NE(buffer->ptr, MAP_FAILED);
+
+	/* Register the region with userfaultfd */
+	reg.range.start = (unsigned long)buffer->ptr;
+	reg.range.len = size;
+	reg.mode = UFFDIO_REGISTER_MODE_MISSING;
+	ret = ioctl(uffd, UFFDIO_REGISTER, &reg);
+	ASSERT_EQ(ret, 0);
+
+	/* Set up the handler thread */
+	uffd_args.uffd = uffd;
+	uffd_args.page_buffer = malloc(self->page_size);
+	ASSERT_NE(uffd_args.page_buffer, NULL);
+	uffd_args.page_size = self->page_size;
+
+	ret = pthread_create(&thread, NULL, uffd_handler_thread, &uffd_args);
+	ASSERT_EQ(ret, 0);
+
+	/*
+	 * Use the unlockable read path which allows the mmap lock to be
+	 * dropped during the fault, enabling userfaultfd resolution.
+	 */
+	ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ_UNLOCKABLE,
+			      buffer, npages);
+	ASSERT_EQ(ret, 0);
+	ASSERT_EQ(buffer->cpages, npages);
+
+	/* Verify the device read the data filled by the uffd handler */
+	ptr = buffer->mirror;
+	for (i = 0; i < size; ++i)
+		ASSERT_EQ(ptr[i], (unsigned char)0xAB);
+
+	pthread_join(thread, NULL);
+	free(uffd_args.page_buffer);
+	close(uffd);
+	hmm_buffer_free(buffer);
+}
+
 TEST_HARNESS_MAIN



^ permalink raw reply related

* [PATCH 2/3] mshv: Use hmm_range_fault_unlockable() for userfaultfd support
From: Stanislav Kinsburskii @ 2026-05-01  1:20 UTC (permalink / raw)
  To: kys, Liam.Howlett, akpm, akpm, david, decui, haiyangz, jgg,
	corbet, leon, longli, ljs, mhocko, rppt, shuah, skhan, surenb,
	vbabka, wei.liu
  Cc: linux-doc, linux-hyperv, linux-kernel, linux-kernel,
	linux-kselftest, linux-mm
In-Reply-To: <177759835313.221039.2807391868456411507.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>

Convert the mshv driver's HMM fault path to use
hmm_range_fault_unlockable() instead of hmm_range_fault(). This enables
userfaultfd-backed guest memory regions by allowing the mmap lock to be
dropped during page fault handling.

Extract the per-VMA walk into a dedicated mshv_region_hmm_fault_walk()
helper. The outer mshv_region_hmm_fault_and_lock() handles the do/while
restart loop: if the lock is dropped during a fault (userfaultfd resolution
or similar) or an invalidation occurs (-EBUSY), the function restarts the
entire walk from the beginning with a fresh notifier_seq, since the VMA
layout may have changed.

Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
---
 drivers/hv/mshv_regions.c |  127 +++++++++++++++++++++++++++++++--------------
 1 file changed, 87 insertions(+), 40 deletions(-)

diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index d09940e88298e..05665446ca6d9 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -565,6 +565,75 @@ int mshv_region_get(struct mshv_region *region)
 	return kref_get_unless_zero(&region->mreg_refcount);
 }
 
+/**
+ * mshv_region_hmm_fault_walk - Walk VMAs and fault in pages for a range
+ * @region : Pointer to the memory region structure
+ * @range  : HMM range structure (caller sets notifier and notifier_seq)
+ * @start  : Starting virtual address of the range to fault (inclusive)
+ * @end    : Ending virtual address of the range to fault (exclusive)
+ * @pfns   : Output array for page frame numbers with HMM flags
+ * @locked : Pointer to lock state; set to 0 if mmap lock was dropped
+ * @do_fault: If true, fault in missing pages; if false, snapshot only
+ *
+ * Iterates through VMAs covering [start, end), collecting page frame
+ * numbers via hmm_range_fault_unlockable() for each VMA segment.
+ * When @do_fault is true, missing pages are faulted in and write faults
+ * are requested only when both the VMA and the hypervisor mapping permit
+ * writes, to avoid breaking copy-on-write semantics on read-only mappings.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int mshv_region_hmm_fault_walk(struct mshv_region *region,
+				      struct hmm_range *range,
+				      unsigned long start,
+				      unsigned long end,
+				      unsigned long *pfns,
+				      int *locked,
+				      bool do_fault)
+{
+	unsigned long cur_start = start;
+	unsigned long *cur_pfns = pfns;
+
+	while (cur_start < end) {
+		struct vm_area_struct *vma;
+
+		vma = vma_lookup(range->notifier->mm, cur_start);
+		if (!vma)
+			return -EFAULT;
+
+		range->hmm_pfns = cur_pfns;
+		range->start = cur_start;
+		range->end = min(vma->vm_end, end);
+		range->default_flags = 0;
+		if (do_fault) {
+			range->default_flags = HMM_PFN_REQ_FAULT;
+			/*
+			 * Only request writable pages from HMM when
+			 * both the VMA and the hypervisor mapping allow
+			 * writes. Without this, hmm_range_fault() would
+			 * trigger COW on read-only mappings (e.g. shared
+			 * zero pages, file-backed pages), breaking
+			 * copy-on-write semantics and potentially
+			 * granting the guest write access to shared host
+			 * pages.
+			 */
+			if ((vma->vm_flags & VM_WRITE) &&
+			    (region->hv_map_flags & HV_MAP_GPA_WRITABLE))
+				range->default_flags |= HMM_PFN_REQ_WRITE;
+		}
+
+		int ret = hmm_range_fault_unlockable(range, locked);
+
+		if (ret || !*locked)
+			return ret;
+
+		cur_start = range->end;
+		cur_pfns += (range->end - range->start) >> PAGE_SHIFT;
+	}
+
+	return 0;
+}
+
 /**
  * mshv_region_hmm_fault_and_lock - Fault in pages across VMAs and lock
  *                                  the memory region
@@ -575,11 +644,9 @@ int mshv_region_get(struct mshv_region *region)
  * @do_fault: If true, fault in missing pages; if false, snapshot only
  *            pages already present in page tables
  *
- * Iterates through VMAs covering [start, end), collecting page frame
- * numbers via hmm_range_fault() for each VMA segment.  When @do_fault
- * is true, missing pages are faulted in and write faults are requested
- * only when both the VMA and the hypervisor mapping permit writes, to
- * avoid breaking copy-on-write semantics on read-only mappings.
+ * Faults in pages covering [start, end) and acquires region->mreg_mutex.
+ * If the mmap lock is dropped during the fault (e.g. by userfaultfd) or
+ * the mmu notifier sequence is invalidated, the entire walk is restarted.
  *
  * On success, returns with region->mreg_mutex held; the caller is
  * responsible for releasing it.  Returns -EBUSY if the mmu notifier
@@ -597,47 +664,27 @@ static int mshv_region_hmm_fault_and_lock(struct mshv_region *region,
 		.notifier = &region->mreg_mni,
 	};
 	struct mm_struct *mm = region->mreg_mni.mm;
+	int locked;
 	int ret;
 
-	range.notifier_seq = mmu_interval_read_begin(range.notifier);
-	mmap_read_lock(mm);
-	while (start < end) {
-		struct vm_area_struct *vma;
+	do {
+		range.notifier_seq = mmu_interval_read_begin(range.notifier);
+		locked = 1;
+		mmap_read_lock(mm);
 
-		vma = vma_lookup(mm, start);
-		if (!vma) {
-			ret = -EFAULT;
-			break;
-		}
+		ret = mshv_region_hmm_fault_walk(region, &range, start, end,
+						 pfns, &locked, do_fault);
 
-		range.hmm_pfns = pfns;
-		range.start = start;
-		range.end = min(vma->vm_end, end);
-		range.default_flags = 0;
-		if (do_fault) {
-			range.default_flags = HMM_PFN_REQ_FAULT;
-			/*
-			 * Only request writable pages from HMM when both
-			 * the VMA and the hypervisor mapping allow writes.
-			 * Without this, hmm_range_fault() would trigger
-			 * COW on read-only mappings (e.g. shared zero
-			 * pages, file-backed pages), breaking
-			 * copy-on-write semantics and potentially granting
-			 * the guest write access to shared host pages.
-			 */
-			if ((vma->vm_flags & VM_WRITE) &&
-			    (region->hv_map_flags & HV_MAP_GPA_WRITABLE))
-				range.default_flags |= HMM_PFN_REQ_WRITE;
-		}
+		if (locked)
+			mmap_read_unlock(mm);
 
-		ret = hmm_range_fault(&range);
-		if (ret)
-			break;
+		/*
+		 * If the lock was dropped (by userfaultfd or similar), restart
+		 * the entire walk with a fresh notifier_seq since the VMA layout
+		 * may have changed. Also restart on -EBUSY (invalidation).
+		 */
+	} while (!locked || ret == -EBUSY);
 
-		start = range.end;
-		pfns += (range.end - range.start) >> PAGE_SHIFT;
-	}
-	mmap_read_unlock(mm);
 	if (ret)
 		return ret;
 



^ permalink raw reply related

* [PATCH 1/3] mm/hmm: Add hmm_range_fault_unlockable() for mmap lock-drop support
From: Stanislav Kinsburskii @ 2026-05-01  1:20 UTC (permalink / raw)
  To: kys, Liam.Howlett, akpm, akpm, david, decui, haiyangz, jgg,
	corbet, leon, longli, ljs, mhocko, rppt, shuah, skhan, surenb,
	vbabka, wei.liu
  Cc: linux-doc, linux-hyperv, linux-kernel, linux-kernel,
	linux-kselftest, linux-mm
In-Reply-To: <177759835313.221039.2807391868456411507.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>

Add hmm_range_fault_unlockable(), a new HMM entry point that allows the
mmap read lock to be dropped during page faults. This follows the
int *locked pattern from get_user_pages_remote() in mm/gup.c: callers
pass an int *locked variable indicating they can handle the lock being
dropped.

When locked is non-NULL, hmm_vma_fault() adds FAULT_FLAG_ALLOW_RETRY
and FAULT_FLAG_KILLABLE to the fault flags passed to handle_mm_fault().
If the fault handler drops the mmap lock (returning VM_FAULT_RETRY or
VM_FAULT_COMPLETED), the function sets *locked = 0 and returns 0,
signalling the caller to restart its walk with a fresh notifier
sequence. Fatal signals are checked before returning, matching GUP
behavior. The caller is responsible for re-acquiring the lock and
restarting from the beginning, since previously collected PFNs may be
stale after the lock was dropped.

The existing hmm_range_fault() is refactored into a thin wrapper that
calls hmm_range_fault_unlockable(range, NULL). Passing NULL means
FAULT_FLAG_ALLOW_RETRY is never set, preserving existing behavior for
all current callers with no functional change.

Faulting hugetlb pages is not supported on the unlockable path: if a
hugetlb page requires faulting, -EFAULT is returned. This is because
walk_hugetlb_range() holds hugetlb_vma_lock_read across the callback
and unconditionally unlocks on return; if the mmap lock is dropped
inside the callback the VMA may be freed, making the walk framework's
unlock a use-after-free. Hugetlb pages already present in page tables
are handled normally.

Documentation/mm/hmm.rst is updated with a new section describing the
unlockable API, its usage pattern, and the hugetlb limitation.

Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
---
 Documentation/mm/hmm.rst |   89 +++++++++++++++++++++++++++++++++++++++++++++
 include/linux/hmm.h      |    1 +
 mm/hmm.c                 |   91 +++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 172 insertions(+), 9 deletions(-)

diff --git a/Documentation/mm/hmm.rst b/Documentation/mm/hmm.rst
index 7d61b7a8b65b7..13874b4dfd5f4 100644
--- a/Documentation/mm/hmm.rst
+++ b/Documentation/mm/hmm.rst
@@ -208,6 +208,95 @@ invalidate() callback. That lock must be held before calling
 mmu_interval_read_retry() to avoid any race with a concurrent CPU page table
 update.
 
+Scalable lock-drop support (hmm_range_fault_unlockable)
+=======================================================
+
+Some page fault handlers (e.g., userfaultfd) require the mmap lock to be
+dropped during fault resolution. Drivers that need to support such mappings
+can use::
+
+  int hmm_range_fault_unlockable(struct hmm_range *range, int *locked);
+
+This follows the same ``int *locked`` pattern used by ``get_user_pages_remote()``
+in ``mm/gup.c``. The caller sets ``*locked = 1`` and holds the mmap read lock
+before calling. If the lock is dropped during the fault (VM_FAULT_RETRY or
+VM_FAULT_COMPLETED), the function returns 0 with ``*locked = 0``, signalling
+the caller to restart its walk with a fresh notifier sequence. The caller is
+responsible for re-acquiring the lock and restarting from the beginning, since
+previously collected PFNs may be stale.
+
+The usage pattern is::
+
+ int driver_populate_range_unlockable(...)
+ {
+      struct hmm_range range;
+      int locked;
+      ...
+
+      range.notifier = &interval_sub;
+      range.start = ...;
+      range.end = ...;
+      range.hmm_pfns = ...;
+
+      if (!mmget_not_zero(interval_sub->notifier.mm))
+          return -EFAULT;
+
+ again:
+      range.notifier_seq = mmu_interval_read_begin(&interval_sub);
+      locked = 1;
+      mmap_read_lock(mm);
+      ret = hmm_range_fault_unlockable(&range, &locked);
+      if (locked)
+          mmap_read_unlock(mm);
+      if (ret) {
+          if (ret == -EBUSY)
+                 goto again;
+          return ret;
+      }
+      if (!locked)
+          goto again;
+
+      take_lock(driver->update);
+      if (mmu_interval_read_retry(&ni, range.notifier_seq) {
+          release_lock(driver->update);
+          goto again;
+      }
+
+      /* Use pfns array content to update device page table,
+       * under the update lock */
+
+      release_lock(driver->update);
+      return 0;
+ }
+
+Passing ``locked = NULL`` to ``hmm_range_fault_unlockable()`` is equivalent to
+calling ``hmm_range_fault()`` — the lock will never be dropped.
+
+Note: hugetlb pages are not supported with the unlockable path. If a hugetlb
+page requires faulting during an ``hmm_range_fault_unlockable()`` call,
+``-EFAULT`` is returned. Hugetlb pages that are already present in page tables
+are handled normally.
+
+This limitation exists because ``walk_hugetlb_range()`` in the page walk
+framework holds ``hugetlb_vma_lock_read`` across the callback and unconditionally
+unlocks on return. If the mmap lock is dropped inside the callback (via
+VM_FAULT_RETRY), the VMA may be freed before the walk framework's unlock,
+resulting in a use-after-free. Possible approaches to lift this limitation in
+the future:
+
+1. Extend the walk framework to allow callbacks to signal that the hugetlb vma
+   lock was dropped (e.g., a flag in ``struct mm_walk`` that tells
+   ``walk_hugetlb_range()`` to skip the unlock).
+
+2. Bypass ``walk_page_range()`` for hugetlb pages in the unlockable path and
+   walk hugetlb page tables directly with custom lock management (similar to
+   how GUP handles hugetlb without the walk framework).
+
+3. Re-acquire the mmap lock before returning from the hugetlb callback (like
+   ``fixup_user_fault()``), ensuring the VMA remains valid for the walk
+   framework's unlock. This changes the "never re-take" contract and would
+   require callers to handle hugetlb differently.
+
 Leverage default_flags and pfn_flags_mask
 =========================================
 
diff --git a/include/linux/hmm.h b/include/linux/hmm.h
index db75ffc949a7a..46e581865c48a 100644
--- a/include/linux/hmm.h
+++ b/include/linux/hmm.h
@@ -123,6 +123,7 @@ struct hmm_range {
  * Please see Documentation/mm/hmm.rst for how to use the range API.
  */
 int hmm_range_fault(struct hmm_range *range);
+int hmm_range_fault_unlockable(struct hmm_range *range, int *locked);
 
 /*
  * HMM_RANGE_DEFAULT_TIMEOUT - default timeout (ms) when waiting for a range
diff --git a/mm/hmm.c b/mm/hmm.c
index 5955f2f0c83db..9bf2fa37f2efd 100644
--- a/mm/hmm.c
+++ b/mm/hmm.c
@@ -33,6 +33,7 @@
 struct hmm_vma_walk {
 	struct hmm_range	*range;
 	unsigned long		last;
+	int			*locked;
 };
 
 enum {
@@ -86,10 +87,28 @@ static int hmm_vma_fault(unsigned long addr, unsigned long end,
 		fault_flags |= FAULT_FLAG_WRITE;
 	}
 
-	for (; addr < end; addr += PAGE_SIZE)
-		if (handle_mm_fault(vma, addr, fault_flags, NULL) &
-		    VM_FAULT_ERROR)
+	if (hmm_vma_walk->locked)
+		fault_flags |= FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE;
+
+	for (; addr < end; addr += PAGE_SIZE) {
+		vm_fault_t ret;
+
+		ret = handle_mm_fault(vma, addr, fault_flags, NULL);
+
+		if (ret & (VM_FAULT_RETRY | VM_FAULT_COMPLETED)) {
+			/*
+			 * The mmap lock has been dropped by the fault handler.
+			 * Record the failing address and signal lock-drop to
+			 * the caller.
+			 */
+			*hmm_vma_walk->locked = 0;
+			hmm_vma_walk->last = addr;
+			return -EAGAIN;
+		}
+
+		if (ret & VM_FAULT_ERROR)
 			return -EFAULT;
+	}
 	return -EBUSY;
 }
 
@@ -566,6 +585,17 @@ static int hmm_vma_walk_hugetlb_entry(pte_t *pte, unsigned long hmask,
 	if (required_fault) {
 		int ret;
 
+		/*
+		 * Faulting hugetlb pages on the unlockable path is not
+		 * supported. The walk framework holds hugetlb_vma_lock_read
+		 * which must be dropped before handle_mm_fault, but if the
+		 * mmap lock is also dropped (VM_FAULT_RETRY), the vma may
+		 * be freed and the walk framework's unconditional unlock
+		 * becomes a use-after-free.
+		 */
+		if (hmm_vma_walk->locked)
+			return -EFAULT;
+
 		spin_unlock(ptl);
 		hugetlb_vma_unlock_read(vma);
 		/*
@@ -655,14 +685,49 @@ static const struct mm_walk_ops hmm_walk_ops = {
  *
  * This is similar to get_user_pages(), except that it can read the page tables
  * without mutating them (ie causing faults).
+ *
+ * The mmap lock must be held by the caller and will remain held on return.
+ * For a variant that allows the mmap lock to be dropped during faults (e.g.,
+ * for userfaultfd support), see hmm_range_fault_unlockable().
  */
 int hmm_range_fault(struct hmm_range *range)
 {
+	return hmm_range_fault_unlockable(range, NULL);
+}
+EXPORT_SYMBOL(hmm_range_fault);
+
+/**
+ * hmm_range_fault_unlockable - fault a range with mmap lock-drop support
+ * @range:	argument structure
+ * @locked:	pointer to lock state variable (input: 1; output: 0 if lock
+ *		was dropped)
+ *
+ * Similar to hmm_range_fault() but allows the mmap lock to be dropped during
+ * page faults. This enables support for userfaultfd-backed mappings and other
+ * cases where handle_mm_fault() may need to release the mmap lock.
+ *
+ * The caller must hold the mmap read lock and set *locked = 1 before calling.
+ * On return:
+ *   - *locked == 1: mmap lock is still held, return value has normal semantics
+ *   - *locked == 0: mmap lock was dropped. The caller must re-acquire the lock
+ *     and restart the operation. Return value is -EBUSY in this case.
+ *
+ * When the lock is dropped internally, this function will attempt to
+ * re-acquire it and retry the fault with FAULT_FLAG_TRIED set. If the retry
+ * also results in lock-drop (possible but unusual), or if a fatal signal is
+ * pending, the function returns with *locked == 0.
+ *
+ * Returns 0 on success or a negative error code. See hmm_range_fault() for
+ * the full list of possible errors.
+ */
+int hmm_range_fault_unlockable(struct hmm_range *range, int *locked)
+{
+	struct mm_struct *mm = range->notifier->mm;
 	struct hmm_vma_walk hmm_vma_walk = {
 		.range = range,
 		.last = range->start,
+		.locked = locked,
 	};
-	struct mm_struct *mm = range->notifier->mm;
 	int ret;
 
 	mmap_assert_locked(mm);
@@ -674,16 +739,24 @@ int hmm_range_fault(struct hmm_range *range)
 			return -EBUSY;
 		ret = walk_page_range(mm, hmm_vma_walk.last, range->end,
 				      &hmm_walk_ops, &hmm_vma_walk);
+		if (ret == -EAGAIN) {
+			/*
+			 * The mmap lock was dropped during the fault
+			 * (e.g. userfaultfd). Signal the caller to restart
+			 * by returning with *locked = 0.
+			 */
+			if (fatal_signal_pending(current))
+				return -EINTR;
+			return 0;
+		}
 		/*
-		 * When -EBUSY is returned the loop restarts with
-		 * hmm_vma_walk.last set to an address that has not been stored
-		 * in pfns. All entries < last in the pfn array are set to their
-		 * output, and all >= are still at their input values.
+		 * -EBUSY: page table changed during the walk.
+		 * Restart from hmm_vma_walk.last.
 		 */
 	} while (ret == -EBUSY);
 	return ret;
 }
-EXPORT_SYMBOL(hmm_range_fault);
+EXPORT_SYMBOL(hmm_range_fault_unlockable);
 
 /**
  * hmm_dma_map_alloc - Allocate HMM map structure



^ permalink raw reply related

* [PATCH 0/3] mm/hmm: Add mmap lock-drop support for userfaultfd-backed mappings
From: Stanislav Kinsburskii @ 2026-05-01  1:20 UTC (permalink / raw)
  To: kys, Liam.Howlett, akpm, akpm, david, decui, haiyangz, jgg,
	corbet, leon, longli, ljs, mhocko, rppt, shuah, skhan, surenb,
	vbabka, wei.liu
  Cc: linux-doc, linux-hyperv, linux-kernel, linux-kernel,
	linux-kselftest, linux-mm

This series extends the HMM framework to support userfaultfd-backed memory
by allowing the mmap read lock to be dropped during hmm_range_fault().

Some page fault handlers — most notably userfaultfd — require the mmap lock
to be released so that userspace can resolve the fault. The current HMM
interface never sets FAULT_FLAG_ALLOW_RETRY, making it impossible to fault
in pages from userfaultfd-registered regions.

This series follows the established int *locked pattern from
get_user_pages_remote() in mm/gup.c. A new entry point,
hmm_range_fault_unlockable(), accepts an int *locked parameter. When the
mmap lock is dropped during fault resolution (VM_FAULT_RETRY or
VM_FAULT_COMPLETED), the function returns 0 with *locked = 0, signalling
the caller to restart its walk. The existing hmm_range_fault() is
refactored into a thin wrapper that passes NULL, preserving current
behavior for all existing callers.

Faulting hugetlb pages on the unlockable path is not supported because
walk_hugetlb_range() unconditionally holds and releases
hugetlb_vma_lock_read across the callback; if the mmap lock is dropped
inside the callback, the VMA may be freed before the walk framework's
unlock. Hugetlb pages already present in page tables are handled normally.
Possible approaches to lift this limitation are documented in
Documentation/mm/hmm.rst.

Patch 1 adds hmm_range_fault_unlockable() to the HMM core, refactors
hmm_range_fault() into a wrapper, and updates Documentation/mm/hmm.rst.
Patch 2 converts the mshv driver to use the new API, enabling
userfaultfd-backed guest memory regions. Patch 3 adds a selftest exercising
the unlockable path with a userfaultfd handler that fills pages via
UFFDIO_COPY.

---

Stanislav Kinsburskii (3):
      mm/hmm: Add hmm_range_fault_unlockable() for mmap lock-drop support
      mshv: Use hmm_range_fault_unlockable() for userfaultfd support
      selftests/mm: Add userfaultfd test for HMM unlockable path


 Documentation/mm/hmm.rst               |   89 +++++++++++++++++++++
 drivers/hv/mshv_regions.c              |  127 +++++++++++++++++++++----------
 include/linux/hmm.h                    |    1 
 lib/test_hmm.c                         |  122 +++++++++++++++++++++++++++++
 lib/test_hmm_uapi.h                    |    1 
 mm/hmm.c                               |   91 ++++++++++++++++++++--
 tools/testing/selftests/mm/hmm-tests.c |  133 ++++++++++++++++++++++++++++++++
 7 files changed, 515 insertions(+), 49 deletions(-)


^ permalink raw reply

* [PATCH 2/2] Docs/admin-guide/mm/damon/reclaim: update for autotune_monitoring_intervals
From: SeongJae Park @ 2026-05-01  1:17 UTC (permalink / raw)
  To: Andrew Morton
  Cc: SeongJae Park, Liam R. Howlett, David Hildenbrand,
	Jonathan Corbet, Lorenzo Stoakes, Michal Hocko, Mike Rapoport,
	Shuah Khan, Suren Baghdasaryan, Vlastimil Babka, damon, linux-doc,
	linux-kernel, linux-mm
In-Reply-To: <20260501011740.81988-1-sj@kernel.org>

Update DAMON_RECLAIM usage document for the newly added monitoring
intervals auto-tuning enablement parameter.

Signed-off-by: SeongJae Park <sj@kernel.org>
---
 Documentation/admin-guide/mm/damon/reclaim.rst | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/Documentation/admin-guide/mm/damon/reclaim.rst b/Documentation/admin-guide/mm/damon/reclaim.rst
index 57ab8b1876506..ec7e3e32b4ac6 100644
--- a/Documentation/admin-guide/mm/damon/reclaim.rst
+++ b/Documentation/admin-guide/mm/damon/reclaim.rst
@@ -85,6 +85,17 @@ identifies the region as cold, and reclaims it.
 
 120 seconds by default.
 
+autotune_monitoring_intervals
+-----------------------------
+
+If this parameter is set as ``Y``, DAMON_RECLAIM automatically tunes DAMON's
+sampling and aggregation intervals.  The auto-tuning aims to capture meaningful
+amount of access events in each DAMON-snapshot, while keeping the sampling
+interval 5 milliseconds in minimum, and 10 seconds in maximum.  Setting this as
+``N`` disables the auto-tuning.
+
+Disabled by default.
+
 quota_ms
 --------
 
-- 
2.47.3

^ permalink raw reply related

* [PATCH 0/2] mm/damon/reclaim: support monitoring intervals auto-tuning
From: SeongJae Park @ 2026-05-01  1:17 UTC (permalink / raw)
  To: Andrew Morton
  Cc: SeongJae Park, Liam R. Howlett, David Hildenbrand,
	Jonathan Corbet, Lorenzo Stoakes, Michal Hocko, Mike Rapoport,
	Shuah Khan, Suren Baghdasaryan, Vlastimil Babka, damon, linux-doc,
	linux-kernel, linux-mm

The monitoring intervals auto-tuning feature of DAMON has proven to be
useful in multiple environments.  Add a new DAMON_RECLAIM parameter for
supporting the feature, and update the document for the new parameter.

Changes from RFC
- rfc link: https://lore.kernel.org/20260414052855.90123-1-sj@kernel.org
- Add notes about behavioral changes that introduced by the auto-tuning.

SeongJae Park (2):
  mm/damon/reclaim: add autotune_monitoring_intervals parameter
  Docs/admin-guide/mm/damon/reclaim: update for
    autotune_monitoring_intervals

 .../admin-guide/mm/damon/reclaim.rst          | 11 +++++++
 mm/damon/reclaim.c                            | 33 ++++++++++++++++---
 2 files changed, 39 insertions(+), 5 deletions(-)


base-commit: 061bdca57aa1fc22d0f74a7c7bd35c7576194484
-- 
2.47.3

^ permalink raw reply

* Re: [PATCH net-next 07/11] net: devmem: support TX over NETMEM_TX_NO_DMA devices
From: Bobby Eshleman @ 2026-05-01  1:07 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni,
	Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi, Yanteng Si,
	Dongliang Mu, Michael Chan, Pavan Chebbi, Joshua Washington,
	Harshitha Ramamurthy, Saeed Mahameed, Tariq Toukan, Mark Bloch,
	Leon Romanovsky, Alexander Duyck, kernel-team, Daniel Borkmann,
	Nikolay Aleksandrov, Shuah Khan, netdev, linux-doc, linux-kernel,
	linux-rdma, bpf, linux-kselftest, Stanislav Fomichev,
	Mina Almasry, Bobby Eshleman
In-Reply-To: <20260430175724.0c134a0d@kernel.org>

On Thu, Apr 30, 2026 at 05:57:24PM -0700, Jakub Kicinski wrote:
> On Tue, 28 Apr 2026 15:42:04 -0700 Bobby Eshleman wrote:
> >  	shinfo = skb_shinfo(skb);
> > +	if (shinfo->nr_frags == 0)
> > +		goto out;
> 
> Feels tempting to cover the NETMEM_TX_NO_DMA / NETMEM_TX_NONE
> cases here before we even look at the frags?

That sounds good to me (had considered it, but opted out cause I felt it
might look odd with the switch-case that follows). And I'll address the
bug(s) the model called out here/elsewhere too.

Thanks,
Bobby

> 
> > -	if (shinfo->nr_frags > 0) {
> > -		niov = netmem_to_net_iov(skb_frag_netmem(&shinfo->frags[0]));
> > -		if (net_is_devmem_iov(niov) &&
> > -		    READ_ONCE(net_devmem_iov_binding(niov)->dev) != dev)
> > +	niov = netmem_to_net_iov(skb_frag_netmem(&shinfo->frags[0]));
> > +	if (!net_is_devmem_iov(niov))
> > +		goto out;
> > +
> > +	binding = net_devmem_iov_binding(niov);
> > +
> > +	switch (dev->netmem_tx) {
> > +	case NETMEM_TX_DMA:
> > +		if (READ_ONCE(binding->dev) != dev)
> >  			goto out_free;
> > +		break;
> > +	case NETMEM_TX_NO_DMA:
> > +		break;
> > +	default: /* NETMEM_TX_NONE */
> > +		goto out_free;
> >  	}

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox