* Re: [PATCH mm-unstable v19 00/14] khugepaged: add mTHP collapse support
From: Lorenzo Stoakes @ 2026-06-05 18:39 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel,
aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe
In-Reply-To: <177704e1-03b3-4791-9a69-9b83b72d61d5@kernel.org>
On Fri, Jun 05, 2026 at 08:07:02PM +0200, David Hildenbrand (Arm) wrote:
> On 6/5/26 18:14, Nico Pache wrote:
> > The following series provides khugepaged with the capability to collapse
> > anonymous memory regions to mTHPs.
> >
> > To achieve this we generalize the khugepaged functions to no longer depend
> > on PMD_ORDER. Then during the PMD scan, we use a bitmap to track individual
> > pages that are occupied (!none/zero). After the PMD scan is done, we use
> > the bitmap to find the optimal mTHP sizes for the PMD range. The
> > restriction on max_ptes_none is removed during the scan, to make sure we
> > account for the whole PMD range in the bitmap. When no mTHP size is
> > enabled, the legacy behavior of khugepaged is maintained.
> >
> > We currently only support max_ptes_none values of 0 or HPAGE_PMD_NR - 1
> > (ie 511). If any other value is specified, the kernel will emit a warning
> > and mTHP collapse will default to max_ptes_none=0. If a mTHP collapse is
> > attempted, but contains swapped out, or shared pages, we don't perform
> > the collapse.
> > It is now also possible to collapse to mTHPs without requiring the PMD THP
> > size to be enabled. These limitations are to prevent collapse "creep"
> > behavior. This prevents constantly promoting mTHPs to the next available
> > size, which would occur because a collapse introduces more non-zero pages
> > that would satisfy the promotion condition on subsequent scans.
> >
> > Patch 1-2: Generalize hugepage_vma_revalidate and alloc_charge_folio
> > for arbitrary orders.
> > Patch 3: Rework max_ptes_* handling into helper functions
> > Patch 4: Generalize __collapse_huge_page_* for mTHP support
> > Patch 5: Require collapse_huge_page to enter/exit with the lock dropped
> > Patch 6: Generalize collapse_huge_page for mTHP collapse
> > Patch 7: Skip collapsing mTHP to smaller orders
> > Patch 8-9: Add per-order mTHP statistics and tracepoints
> > Patch 10: Introduce collapse_possible_orders helper functions
> > Patch 11-13: Introduce bitmap and mTHP collapse support, fully enabled
> > Patch 14: Documentation
> >
>
> Went through it and didn't find any blockers. Let's wait for Lorenzo's assessment.
>
> If he also doesn't find anything major, I think we can move forward with merging
> it and handle smaller things as follow-ups.
All LGTM :)
Unless sashiko tells us about something utterly broken (trivial things meh,
existing things meh), or we see some massive breakage in testing, I
think... *drum roll*
We're good to take this this cycle :)
Thanks Nico for your patience during this process and obviously David and Lance
and all the other reviews for taking part.
We got there in the end :)
>
> --
> Cheers,
>
> David
Cheers, Lorenzo
^ permalink raw reply
* Re: [PATCH mm-unstable v19 11/14] mm/khugepaged: Introduce mTHP collapse support
From: Lorenzo Stoakes @ 2026-06-05 18:38 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
akpm, anshuman.khandual, apopple, baohua, baolin.wang, byungchul,
catalin.marinas, cl, corbet, dave.hansen, david, dev.jain, gourry,
hannes, hughd, jack, jackmanb, jannh, jglisse, joshua.hahnjy, kas,
lance.yang, liam, mathieu.desnoyers, matthew.brost, mhiramat,
mhocko, peterx, pfalcato, rakie.kim, raquini, rdunlap,
richard.weiyang, rientjes, rostedt, rppt, ryan.roberts, shivankg,
sunnanyong, surenb, thomas.hellstrom, tiwai, usamaarif642, vbabka,
vishal.moola, wangkefeng.wang, will, willy, yang, ying.huang, ziy,
zokeefe
In-Reply-To: <20260605161422.213817-12-npache@redhat.com>
On Fri, Jun 05, 2026 at 10:14:18AM -0600, Nico Pache wrote:
> Enable khugepaged to collapse to mTHP orders. This patch implements the
> main scanning logic using a bitmap to track occupied pages and the
> algorithm to find optimal collapse sizes.
>
> Previous to this patch, PMD collapse had 3 main phases, a light weight
> scanning phase (mmap_read_lock) that determines a potential PMD
> collapse, an alloc phase (mmap unlocked), then finally heavier collapse
> phase (mmap_write_lock).
>
> To enabled mTHP collapse we make the following changes:
>
> During PMD scan phase, track occupied pages in a bitmap. When mTHP
> orders are enabled, we remove the restriction of max_ptes_none during the
> scan phase to avoid missing potential mTHP collapse candidates. Once we
> have scanned the full PMD range and updated the bitmap to track occupied
> pages, we use the bitmap to find the optimal mTHP size.
>
> Implement mthp_collapse() to walk forward through the bitmap and
> determine the best eligible order for each naturally-aligned region. The
> algorithm starts at the beginning of the PMD range and, for each offset,
> tries the highest order that fits the alignment. If the number of
> occupied PTEs in that region satisfies the max_ptes_none threshold for
> that order, a collapse is attempted. On failure, the order is
> decremented and the same offset is retried at the next smaller size. Once
> the smallest enabled order is exhausted (or a collapse succeeds), the
> offset advances past the region just processed, and the next attempt
> starts at the highest order permitted by the new offset's natural
> alignment.
I think still it might have been nice to discuss why we are not
e.g. greedily trying to find the biggest possible mTHP size (if we did, we
would try the highest offset first), but we can save that for adding some
documentation somewhere later tbh.
This commit message is long enough as it is :>)
>
> The algorithm works as follows:
> 1) set offset=0 and order=HPAGE_PMD_ORDER
> 2) if the order is not enabled, go to step (5)
> 3) count occupied PTEs in the (offset, order) range using
> bitmap_weight_from()
> 4) if the count satisfies the max_ptes_none threshold, attempt
> collapse; on success, advance to step (6)
> 5) if a smaller enabled order exists, decrement order and retry
> from step (2) at the same offset
> 6) advance offset past the current region and compute the next
> order from the new offset's natural alignment via __ffs(offset),
> capped at HPAGE_PMD_ORDER
> 7) repeat from step (2) until the full PMD range is covered
>
> mTHP collapses reject regions containing swapped out or shared pages.
> This is because adding new entries can lead to new none pages, and these
> may lead to constant promotion into a higher order mTHP. A similar
> issue can occur with "max_ptes_none > HPAGE_PMD_NR/2" due to a collapse
> introducing at least 2x the number of pages, and on a future scan will
> satisfy the promotion condition once again. This issue is prevented via
> the collapse_max_ptes_none() function which imposes the max_ptes_none
> restrictions above.
>
> We currently only support mTHP collapse for max_ptes_none values of 0
> and HPAGE_PMD_NR - 1. resulting in the following behavior:
>
> - max_ptes_none=0: Never introduce new empty pages during collapse
> - max_ptes_none=HPAGE_PMD_NR-1: Always try collapse to the highest
> available mTHP order
>
> Any other max_ptes_none value will emit a warning and default mTHP
> collapse to max_ptes_none=0. There should be no behavior change for PMD
> collapse.
>
> Once we determine what mTHP sizes fits best in that PMD range a collapse
> is attempted. A minimum collapse order of 2 is used as this is the lowest
> order supported by anon memory as defined by THP_ORDERS_ALL_ANON.
>
> Currently madv_collapse is not supported and will only attempt PMD
> collapse.
>
> We can also remove the check for is_khugepaged inside the PMD scan as
> the collapse_max_ptes_none() function handles this logic now.
It'd be nice to have kept the ASCII diagram here too :'( but this is fine,
>
> Signed-off-by: Nico Pache <npache@redhat.com>
This all LGTM, and we can fix up any issues that arise later if anything
does break. So:
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> mm/khugepaged.c | 146 +++++++++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 138 insertions(+), 8 deletions(-)
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index ec886a031952..430047316f43 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -99,6 +99,8 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_slots_hash, MM_SLOTS_HASH_BITS);
>
> static struct kmem_cache *mm_slot_cache __ro_after_init;
>
> +#define KHUGEPAGED_MIN_MTHP_ORDER 2
> +
> struct collapse_control {
> bool is_khugepaged;
>
> @@ -110,6 +112,9 @@ struct collapse_control {
>
> /* nodemask for allocation fallback */
> nodemask_t alloc_nmask;
> +
> + /* Each bit represents a single occupied (!none/zero) page. */
> + DECLARE_BITMAP(mthp_present_ptes, MAX_PTRS_PER_PTE);
> };
>
> /**
> @@ -1440,20 +1445,130 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
> return result;
> }
>
> +/* Return the highest naturally aligned order that fits at @offset within a PMD. */
> +static unsigned int max_order_from_offset(unsigned int offset)
> +{
> + if (offset == 0)
> + return HPAGE_PMD_ORDER;
> +
> + return min_t(unsigned int, __ffs(offset), HPAGE_PMD_ORDER);
> +}
Thanks this is better! I wonder if we can ever actually see an
__ffs(offset) that's > HPAGE_PMD_ORDER but probably better safe than sorry
here with the min_t.
> +
> +/*
> + * mthp_collapse() consumes the bitmap that is generated during
> + * collapse_scan_pmd() to determine what regions and mTHP orders fit best.
> + *
> + * Each bit in cc->mthp_present_ptes represents a single occupied (!none/zero)
> + * page. We start at the PMD order and check if it is eligible for collapse;
> + * if not, we check the left and right halves of the PTE page table we are
> + * examining at a lower order.
> + *
> + * For each of these, we determine how many PTE entries are occupied in the
> + * range of PTE entries we propose to collapse, then we compare this to a
> + * threshold number of PTE entries which would need to be occupied for a
> + * collapse to be permitted at that order (accounting for max_ptes_none).
> + *
> + * If a collapse is permitted, we attempt to collapse the PTE range into a
> + * mTHP.
> + */
> +static enum scan_result mthp_collapse(struct mm_struct *mm,
> + unsigned long address, int referenced, int unmapped,
> + struct collapse_control *cc, unsigned long enabled_orders)
> +{
> + unsigned int nr_occupied_ptes, nr_ptes, max_ptes_none;
> + enum scan_result last_result = SCAN_FAIL;
> + int collapsed = 0;
> + bool alloc_failed = false;
> + unsigned long collapse_address;
> + unsigned int offset = 0;
> + unsigned int order = HPAGE_PMD_ORDER;
> +
> + while (offset < HPAGE_PMD_NR) {
> + nr_ptes = 1UL << order;
> +
> + if (!test_bit(order, &enabled_orders))
> + goto next_order;
> +
> + max_ptes_none = collapse_max_ptes_none(cc, NULL, order);
> + nr_occupied_ptes = bitmap_weight_from(cc->mthp_present_ptes, offset,
> + offset + nr_ptes);
> +
> + if (nr_occupied_ptes >= nr_ptes - max_ptes_none) {
> + enum scan_result ret;
> +
> + collapse_address = address + offset * PAGE_SIZE;
> + ret = collapse_huge_page(mm, collapse_address, referenced,
> + unmapped, cc, order);
> + switch (ret) {
> + /* Cases where we continue to next collapse candidate */
> + case SCAN_SUCCEED:
> + collapsed += nr_ptes;
> + fallthrough;
> + case SCAN_PTE_MAPPED_HUGEPAGE:
> + goto next_offset;
> + /* Cases where lower orders might still succeed */
> + case SCAN_ALLOC_HUGE_PAGE_FAIL:
> + alloc_failed = true;
> + last_result = ret;
> + goto next_order;
> + /* Cases where no further collapse is possible */
> + case SCAN_PMD_MAPPED:
> + fallthrough;
> + default:
> + last_result = ret;
> + goto done;
> + }
> + }
> +
> +next_order:
> + /*
> + * Continue with the next smaller order if there is still
> + * any smaller order enabled. When at the smallest order
> + * we must always move to the next offset.
> + */
> + if (order > KHUGEPAGED_MIN_MTHP_ORDER &&
> + (enabled_orders & GENMASK(order - 1, 0))) {
Honestly wasn't aware of GENMASK() before :)
> + order--;
> + continue;
> + }
> +next_offset:
> + /*
> + * Advance past the region we just processed and determine the
> + * highest order we can attempt next. Since huge pages must be
> + * naturally aligned, the max order we can attempt next is
> + * limited by the alignment of the new offset.
> + * E.g. if we collapsed a order-2 mTHP at offset 0, offset
> + * becomes 4 and __ffs(4) == 2, so the next attempt starts at
> + * order 2.
> + */
Great comment thanks!
> + offset += nr_ptes;
> + order = max_order_from_offset(offset);
> + }
> +done:
> + if (collapsed)
> + return SCAN_SUCCEED;
> + if (alloc_failed)
> + return SCAN_ALLOC_HUGE_PAGE_FAIL;
> + return last_result;
> +}
> +
> static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> struct vm_area_struct *vma, unsigned long start_addr,
> bool *lock_dropped, struct collapse_control *cc)
> {
> - const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);
> const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER);
> const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
> + unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);
> + enum tva_type tva_flags = cc->is_khugepaged ? TVA_KHUGEPAGED : TVA_FORCED_COLLAPSE;
> pmd_t *pmd;
> - pte_t *pte, *_pte;
> + pte_t *pte, *_pte, pteval;
> + int i;
> int none_or_zero = 0, shared = 0, referenced = 0;
> enum scan_result result = SCAN_FAIL;
> struct page *page = NULL;
> struct folio *folio = NULL;
> unsigned long addr;
> + unsigned long enabled_orders;
> spinlock_t *ptl;
> int node = NUMA_NO_NODE, unmapped = 0;
>
> @@ -1465,8 +1580,19 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> goto out;
> }
>
> + bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE);
> memset(cc->node_load, 0, sizeof(cc->node_load));
> nodes_clear(cc->alloc_nmask);
> +
> + enabled_orders = collapse_possible_orders(vma, vma->vm_flags, tva_flags);
> +
> + /*
> + * If PMD is the only enabled order, enforce max_ptes_none, otherwise
> + * scan all pages to populate the bitmap for mTHP collapse.
> + */
> + if (enabled_orders != BIT(HPAGE_PMD_ORDER))
> + max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
> +
> pte = pte_offset_map_lock(mm, pmd, start_addr, &ptl);
> if (!pte) {
> cc->progress++;
> @@ -1474,11 +1600,13 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> goto out;
> }
>
> - for (addr = start_addr, _pte = pte; _pte < pte + HPAGE_PMD_NR;
> - _pte++, addr += PAGE_SIZE) {
> + for (i = 0; i < HPAGE_PMD_NR; i++) {
> + _pte = pte + i;
> + addr = start_addr + i * PAGE_SIZE;
> + pteval = ptep_get(_pte);
> +
> cc->progress++;
>
> - pte_t pteval = ptep_get(_pte);
> if (pte_none_or_zero(pteval)) {
> if (++none_or_zero > max_ptes_none) {
> result = SCAN_EXCEED_NONE_PTE;
> @@ -1558,6 +1686,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> }
> }
>
> + /* Set bit for occupied pages */
> + __set_bit(i, cc->mthp_present_ptes);
> /*
> * Record which node the original page is from and save this
> * information to cc->node_load[].
> @@ -1616,9 +1746,9 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> if (result == SCAN_SUCCEED) {
> /* collapse_huge_page expects the lock to be dropped before calling */
> mmap_read_unlock(mm);
> - result = collapse_huge_page(mm, start_addr, referenced,
> - unmapped, cc, HPAGE_PMD_ORDER);
> - /* collapse_huge_page will return with the mmap_lock released */
> + result = mthp_collapse(mm, start_addr, referenced,
> + unmapped, cc, enabled_orders);
> + /* mmap_lock was released above, set lock_dropped */
> *lock_dropped = true;
> }
> out:
> --
> 2.54.0
>
Cheers, Lorenzo
^ permalink raw reply
* Re: [PATCH v7 00/42] guest_memfd: In-place conversion support
From: Sean Christopherson @ 2026-06-05 18:27 UTC (permalink / raw)
To: Ackerley Tng
Cc: Ackerley Tng via B4 Relay, aik, andrew.jones, binbin.wu, brauner,
chao.p.peng, david, ira.weiny, jmattson, jthoughton, michael.roth,
oupton, pankaj.gupta, qperret, rick.p.edgecombe, rientjes,
shivankg, steven.price, tabba, willy, wyihan, yan.y.zhao,
forkloop, pratyush, suzuki.poulose, aneesh.kumar, liam,
Paolo Bonzini, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka,
kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco
In-Reply-To: <CAEvNRgHz5GDjq0GqRmpQdHc-X45gCNr39VYWZH-T7XhPEtN5CQ@mail.gmail.com>
On Thu, Jun 04, 2026, Ackerley Tng wrote:
> Sean Christopherson <seanjc@google.com> writes:
> >> + KVM: selftests: Test conversion with elevated page refcount
> >> + Askar pointed out that soon vmsplice may not pin pages. Should I
> >> pin pages through CONFIG_GUP_TEST like in [2]? I prefer not to
> >> take a dependency on CONFIG_GUP_TEST.
> >
> > I'm not exactly excited about taking a dependency on CONFIG_GUP_TEST either, but
> > it probably is the least awful choice. E.g. KVM also pins pages is certain flows,
> > but we're _also_ actively working to remove the need to pin.
> >
> > Hmm, maybe IORING_REGISTER_PBUF_RING? AFAICT, it's almost literally a "pin user
> > memory" syscall.
> >
>
> Hmm that takes a dependency on io_uring, which isn't always compiled
> in. Between CONFIG_IO_URING and CONFIG_GUP_TEST, I'd rather
> CONFIG_GUP_TEST.
Or try both? If it's not a ridiculous amount of work.
^ permalink raw reply
* Re: [PATCH mm-unstable v19 14/14] Documentation: mm: update the admin guide for mTHP collapse
From: Lorenzo Stoakes @ 2026-06-05 18:20 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
akpm, anshuman.khandual, apopple, baohua, baolin.wang, byungchul,
catalin.marinas, cl, corbet, dave.hansen, david, dev.jain, gourry,
hannes, hughd, jack, jackmanb, jannh, jglisse, joshua.hahnjy, kas,
lance.yang, liam, mathieu.desnoyers, matthew.brost, mhiramat,
mhocko, peterx, pfalcato, rakie.kim, raquini, rdunlap,
richard.weiyang, rientjes, rostedt, rppt, ryan.roberts, shivankg,
sunnanyong, surenb, thomas.hellstrom, tiwai, usamaarif642, vbabka,
vishal.moola, wangkefeng.wang, will, willy, yang, ying.huang, ziy,
zokeefe, Bagas Sanjaya
In-Reply-To: <20260605161422.213817-15-npache@redhat.com>
On Fri, Jun 05, 2026 at 10:14:21AM -0600, Nico Pache wrote:
> Now that we can collapse to mTHPs lets update the admin guide to
> reflect these changes and provide proper guidance on how to utilize it.
>
> Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> Reviewed-by: Bagas Sanjaya <bagasdotme@gmail.com>
> Signed-off-by: Nico Pache <npache@redhat.com>
This is completely fine, and no blockers, but just a couple tiny things
below Claude brought up for a possible trivial follow up.
> ---
> Documentation/admin-guide/mm/transhuge.rst | 49 ++++++++++++++--------
> 1 file changed, 32 insertions(+), 17 deletions(-)
>
> diff --git a/Documentation/admin-guide/mm/transhuge.rst b/Documentation/admin-guide/mm/transhuge.rst
> index b98e18c80185..23f8d13c2629 100644
> --- a/Documentation/admin-guide/mm/transhuge.rst
> +++ b/Documentation/admin-guide/mm/transhuge.rst
> @@ -63,7 +63,8 @@ often.
> THP can be enabled system wide or restricted to certain tasks or even
> memory ranges inside task's address space. Unless THP is completely
> disabled, there is ``khugepaged`` daemon that scans memory and
> -collapses sequences of basic pages into PMD-sized huge pages.
> +collapses sequences of basic pages into huge pages of either PMD size
> +or mTHP sizes, if the system is configured to do so.
>
> The THP behaviour is controlled via :ref:`sysfs <thp_sysfs>`
> interface and using madvise(2) and prctl(2) system calls.
> @@ -219,10 +220,10 @@ this behaviour by writing 0 to shrink_underused, and enable it by writing
> echo 0 > /sys/kernel/mm/transparent_hugepage/shrink_underused
> echo 1 > /sys/kernel/mm/transparent_hugepage/shrink_underused
>
> -khugepaged will be automatically started when PMD-sized THP is enabled
> +khugepaged will be automatically started when any THP size is enabled
> (either of the per-size anon control or the top-level control are set
> to "always" or "madvise"), and it'll be automatically shutdown when
> -PMD-sized THP is disabled (when both the per-size anon control and the
> +all THP sizes are disabled (when both the per-size anon control and the
> top-level control are "never")
Claude was very pedantic and said we need a full stop here :P
This is not a blocker, obviously...!
>
> process THP controls
> @@ -265,8 +266,8 @@ Khugepaged controls
> -------------------
>
> .. note::
> - khugepaged currently only searches for opportunities to collapse to
> - PMD-sized THP and no attempt is made to collapse to other THP
> + khugepaged currently only searches for opportunities to collapse file/shmem
> + to PMD-sized THP. Only anonymous memory will attempt to collapse to other THP
> sizes.
>
> khugepaged runs usually at low frequency so while one may not want to
> @@ -296,11 +297,11 @@ allocation failure to throttle the next allocation attempt::
> The khugepaged progress can be seen in the number of pages collapsed (note
> that this counter may not be an exact count of the number of pages
> collapsed, since "collapsed" could mean multiple things: (1) A PTE mapping
> -being replaced by a PMD mapping, or (2) All 4K physical pages replaced by
> -one 2M hugepage. Each may happen independently, or together, depending on
> -the type of memory and the failures that occur. As such, this value should
> -be interpreted roughly as a sign of progress, and counters in /proc/vmstat
> -consulted for more accurate accounting)::
> +being replaced by a PMD mapping, or (2) physical pages replaced by one
> +hugepage of various sizes (PMD-sized or mTHP). Each may happen independently,
> +or together, depending on the type of memory and the failures that occur.
> +As such, this value should be interpreted roughly as a sign of progress,
> +and counters in /proc/vmstat consulted for more accurate accounting)::
So Claude said maybe it's worth mentioning that the per-mTHP counters are only
actually exposed through
/sys/kernel/mm/transparent_hugepage/hugepages-<size>kB/stats/ and maybe worth
mentioning here too?
>
> /sys/kernel/mm/transparent_hugepage/khugepaged/pages_collapsed
>
> @@ -308,16 +309,21 @@ for each pass::
>
> /sys/kernel/mm/transparent_hugepage/khugepaged/full_scans
>
> -``max_ptes_none`` specifies how many extra small pages (that are
> -not already mapped) can be allocated when collapsing a group
> -of small pages into one large page::
> +``max_ptes_none`` specifies how many empty (none/zero) pages are allowed
> +when collapsing a group of small pages into one large page::
>
> /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none
>
> -A higher value leads to use additional memory for programs.
> -A lower value leads to gain less thp performance. Value of
> -max_ptes_none can waste cpu time very little, you can
> -ignore it.
> +For PMD-sized THP collapse, this directly limits the number of empty pages
> +allowed in the 2MB region.
> +
> +For mTHP collapse, only 0 or (HPAGE_PMD_NR - 1) are supported. At
> +HPAGE_PMD_NR - 1, we collapse to the highest possible order. Any intermediate
> +value will emit a warning and mTHP collapse will default to max_ptes_none=0.
> +
> +A higher value allows more empty pages, potentially leading to more memory
> +usage but better THP performance. A lower value is more conservative and
> +may result in fewer THP collapses.
>
> ``max_ptes_swap`` specifies how many pages can be brought in from
> swap when collapsing a group of pages into a transparent huge page::
> @@ -337,6 +343,15 @@ that THP is shared. Exceeding the number would block the collapse::
>
> A higher value may increase memory footprint for some workloads.
>
> +.. note::
> + For mTHP collapse, khugepaged does not support collapsing regions that
> + contain shared or swapped out pages, as this could lead to continuous
> + promotion to higher orders. The collapse will fail if any shared or
> + swapped PTEs are encountered during the scan.
> +
> + Currently, madvise_collapse only supports collapsing to PMD-sized THPs
> + and does not attempt mTHP collapses.
> +
> Boot parameters
> ===============
>
> --
> 2.54.0
>
Cheers, Lorenzo
^ permalink raw reply
* Re: [PATCH mm-unstable v19 06/14] mm/khugepaged: generalize collapse_huge_page for mTHP collapse
From: Lorenzo Stoakes @ 2026-06-05 18:18 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
akpm, anshuman.khandual, apopple, baohua, baolin.wang, byungchul,
catalin.marinas, cl, corbet, dave.hansen, david, dev.jain, gourry,
hannes, hughd, jack, jackmanb, jannh, jglisse, joshua.hahnjy, kas,
lance.yang, liam, mathieu.desnoyers, matthew.brost, mhiramat,
mhocko, peterx, pfalcato, rakie.kim, raquini, rdunlap,
richard.weiyang, rientjes, rostedt, rppt, ryan.roberts, shivankg,
sunnanyong, surenb, thomas.hellstrom, tiwai, usamaarif642, vbabka,
vishal.moola, wangkefeng.wang, will, willy, yang, ying.huang, ziy,
zokeefe
In-Reply-To: <20260605161422.213817-7-npache@redhat.com>
On Fri, Jun 05, 2026 at 10:14:13AM -0600, Nico Pache wrote:
> Pass an order to collapse_huge_page to support collapsing anon memory to
> arbitrary orders within a PMD. order indicates what mTHP size we are
> attempting to collapse to.
>
> For non-PMD collapse we must leave the anon VMA write locked until after
> we collapse the mTHP-- in the PMD case all the pages are isolated, but in
> the mTHP case this is not true, and we must keep the lock to prevent
> access/changes to the page tables. This can happen if the rmap walkers hit
> a pmd_none while the PMD entry is currently unavailable due to being
> temporarily removed during the collapse phase.
>
> To properly establish the page table hierarchy without violating any
> expectations from certain architectures (e.g. MIPS), we must make sure to
> have the PMD reinstalled before the PTEs, and hold both PTE/PMD locks
> before calling update_mmu_cache_range() (if they are distinct locks).
>
> Signed-off-by: Nico Pache <npache@redhat.com>
In case Andrew missing my tag in the lengthily reply in [0]:
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
:)
(Though probably he has a script that does this for him!)
[0]:https://lore.kernel.org/linux-mm/aiMNT7fBUkZS1EJK@lucifer/
Cheers, Lorenzo
> ---
> mm/khugepaged.c | 105 ++++++++++++++++++++++++++++++------------------
> 1 file changed, 67 insertions(+), 38 deletions(-)
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index e4b2ca77ecf6..c2769d82a719 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -1228,34 +1228,36 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru
> * while allocating a THP, as that could trigger direct reclaim/compaction.
> * Note that the VMA must be rechecked after grabbing the mmap_lock again.
> */
> -static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long address,
> - int referenced, int unmapped, struct collapse_control *cc)
> +static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long start_addr,
> + int referenced, int unmapped, struct collapse_control *cc,
> + unsigned int order)
> {
> + const unsigned long pmd_addr = start_addr & HPAGE_PMD_MASK;
> + const unsigned long end_addr = start_addr + (PAGE_SIZE << order);
> LIST_HEAD(compound_pagelist);
> pmd_t *pmd, _pmd;
> - pte_t *pte;
> + pte_t *pte = NULL;
> pgtable_t pgtable;
> struct folio *folio;
> spinlock_t *pmd_ptl, *pte_ptl;
> enum scan_result result = SCAN_FAIL;
> struct vm_area_struct *vma;
> struct mmu_notifier_range range;
> + bool anon_vma_locked = false;
>
> - VM_BUG_ON(address & ~HPAGE_PMD_MASK);
> -
> - result = alloc_charge_folio(&folio, mm, cc, HPAGE_PMD_ORDER);
> + result = alloc_charge_folio(&folio, mm, cc, order);
> if (result != SCAN_SUCCEED)
> goto out_nolock;
>
> mmap_read_lock(mm);
> - result = hugepage_vma_revalidate(mm, address, true, &vma, cc,
> - HPAGE_PMD_ORDER);
> + result = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
> + &vma, cc, order);
> if (result != SCAN_SUCCEED) {
> mmap_read_unlock(mm);
> goto out_nolock;
> }
>
> - result = find_pmd_or_thp_or_none(mm, address, &pmd);
> + result = find_pmd_or_thp_or_none(mm, pmd_addr, &pmd);
> if (result != SCAN_SUCCEED) {
> mmap_read_unlock(mm);
> goto out_nolock;
> @@ -1267,8 +1269,8 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
> * released when it fails. So we jump out_nolock directly in
> * that case. Continuing to collapse causes inconsistency.
> */
> - result = __collapse_huge_page_swapin(mm, vma, address, pmd,
> - referenced, HPAGE_PMD_ORDER);
> + result = __collapse_huge_page_swapin(mm, vma, start_addr, pmd,
> + referenced, order);
> if (result != SCAN_SUCCEED)
> goto out_nolock;
> }
> @@ -1283,20 +1285,28 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
> * mmap_lock.
> */
> mmap_write_lock(mm);
> - result = hugepage_vma_revalidate(mm, address, true, &vma, cc,
> - HPAGE_PMD_ORDER);
> + result = hugepage_vma_revalidate(mm, pmd_addr, /*expect_anon=*/ true,
> + &vma, cc, order);
> if (result != SCAN_SUCCEED)
> goto out_up_write;
> /* check if the pmd is still valid */
> vma_start_write(vma);
> - result = check_pmd_still_valid(mm, address, pmd);
> + result = check_pmd_still_valid(mm, pmd_addr, pmd);
> if (result != SCAN_SUCCEED)
> goto out_up_write;
>
> anon_vma_lock_write(vma->anon_vma);
> + anon_vma_locked = true;
>
> - mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, mm, address,
> - address + HPAGE_PMD_SIZE);
> + /*
> + * Only notify about the PTE range we will actually modify. While we
> + * temporary unmap the whole PTE table for mTHP collapse, we'll remap
> + * it later, leaving other PTEs effectively unmodified. The locks we
> + * hold prevent anybody from stumbling over such temporarily unmapped
> + * PTE tables.
> + */
> + mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, mm, start_addr,
> + end_addr);
> mmu_notifier_invalidate_range_start(&range);
>
> pmd_ptl = pmd_lock(mm, pmd); /* probably unnecessary */
> @@ -1308,26 +1318,23 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
> * Parallel GUP-fast is fine since GUP-fast will back off when
> * it detects PMD is changed.
> */
> - _pmd = pmdp_collapse_flush(vma, address, pmd);
> + _pmd = pmdp_collapse_flush(vma, pmd_addr, pmd);
> spin_unlock(pmd_ptl);
> mmu_notifier_invalidate_range_end(&range);
> tlb_remove_table_sync_one();
>
> - pte = pte_offset_map_lock(mm, &_pmd, address, &pte_ptl);
> + pte = pte_offset_map_lock(mm, &_pmd, start_addr, &pte_ptl);
> if (pte) {
> - result = __collapse_huge_page_isolate(vma, address, pte, cc,
> - HPAGE_PMD_ORDER,
> - &compound_pagelist);
> + result = __collapse_huge_page_isolate(vma, start_addr, pte, cc,
> + order, &compound_pagelist);
> spin_unlock(pte_ptl);
> } else {
> result = SCAN_NO_PTE_TABLE;
> }
>
> if (unlikely(result != SCAN_SUCCEED)) {
> - if (pte)
> - pte_unmap(pte);
> spin_lock(pmd_ptl);
> - BUG_ON(!pmd_none(*pmd));
> + VM_WARN_ON_ONCE(!pmd_none(*pmd));
> /*
> * We can only use set_pmd_at when establishing
> * hugepmds and never for establishing regular pmds that
> @@ -1335,21 +1342,24 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
> */
> pmd_populate(mm, pmd, pmd_pgtable(_pmd));
> spin_unlock(pmd_ptl);
> - anon_vma_unlock_write(vma->anon_vma);
> goto out_up_write;
> }
>
> /*
> - * All pages are isolated and locked so anon_vma rmap
> - * can't run anymore.
> + * For PMD collapse all pages are isolated and locked so anon_vma
> + * rmap can't run anymore. For mTHP collapse the PMD entry has been
> + * removed and not all pages are isolated and locked, so we must hold
> + * the lock to prevent neighboring folios from attempting to access
> + * this PMD until its reinstalled.
> */
> - anon_vma_unlock_write(vma->anon_vma);
> + if (is_pmd_order(order)) {
> + anon_vma_unlock_write(vma->anon_vma);
> + anon_vma_locked = false;
> + }
>
> result = __collapse_huge_page_copy(pte, folio, pmd, _pmd,
> - vma, address, pte_ptl,
> - HPAGE_PMD_ORDER,
> - &compound_pagelist);
> - pte_unmap(pte);
> + vma, start_addr, pte_ptl,
> + order, &compound_pagelist);
> if (unlikely(result != SCAN_SUCCEED))
> goto out_up_write;
>
> @@ -1359,18 +1369,37 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
> * write.
> */
> __folio_mark_uptodate(folio);
> - pgtable = pmd_pgtable(_pmd);
> -
> spin_lock(pmd_ptl);
> - BUG_ON(!pmd_none(*pmd));
> - pgtable_trans_huge_deposit(mm, pmd, pgtable);
> - map_anon_folio_pmd_nopf(folio, pmd, vma, address);
> + VM_WARN_ON_ONCE(!pmd_none(*pmd));
> + if (is_pmd_order(order)) {
> + pgtable = pmd_pgtable(_pmd);
> + pgtable_trans_huge_deposit(mm, pmd, pgtable);
> + map_anon_folio_pmd_nopf(folio, pmd, vma, pmd_addr);
> + } else {
> + /*
> + * Some architectures (e.g. MIPS) walk the live page table in
> + * their implementation. update_mmu_cache_range() must be called
> + * with a valid page table hierarchy and the PTE lock held.
> + * Acquire it nested inside pmd_ptl when they are distinct locks.
> + */
> + if (pte_ptl != pmd_ptl)
> + spin_lock_nested(pte_ptl, SINGLE_DEPTH_NESTING);
> + pmd_populate(mm, pmd, pmd_pgtable(_pmd));
> + map_anon_folio_pte_nopf(folio, pte, vma, start_addr,
> + /*uffd_wp=*/ false);
> + if (pte_ptl != pmd_ptl)
> + spin_unlock(pte_ptl);
> + }
> spin_unlock(pmd_ptl);
>
> folio = NULL;
>
> result = SCAN_SUCCEED;
> out_up_write:
> + if (anon_vma_locked)
> + anon_vma_unlock_write(vma->anon_vma);
> + if (pte)
> + pte_unmap(pte);
> mmap_write_unlock(mm);
> out_nolock:
> if (folio)
> @@ -1550,7 +1579,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> /* collapse_huge_page expects the lock to be dropped before calling */
> mmap_read_unlock(mm);
> result = collapse_huge_page(mm, start_addr, referenced,
> - unmapped, cc);
> + unmapped, cc, HPAGE_PMD_ORDER);
> /* collapse_huge_page will return with the mmap_lock released */
> *lock_dropped = true;
> }
> --
> 2.54.0
>
^ permalink raw reply
* Re: [PATCH mm-unstable v19 12/14] mm/khugepaged: avoid unnecessary mTHP collapse attempts
From: Lorenzo Stoakes @ 2026-06-05 18:16 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel,
aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe, Usama Arif
In-Reply-To: <44b12ede-1e16-47f5-9051-27fa4ea34236@kernel.org>
On Fri, Jun 05, 2026 at 07:49:34PM +0200, David Hildenbrand (Arm) wrote:
> On 6/5/26 18:14, Nico Pache wrote:
> > There are cases where, if an attempted collapse fails, all subsequent
> > orders are guaranteed to also fail. Avoid these collapse attempts by
> > bailing out early.
> >
> > Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> > Acked-by: Usama Arif <usama.arif@linux.dev>
> > Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> > Signed-off-by: Nico Pache <npache@redhat.com>
> > ---
> > mm/khugepaged.c | 13 +++++++++++++
> > 1 file changed, 13 insertions(+)
> >
> > diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> > index 430047316f43..7de92b28dd30 100644
> > --- a/mm/khugepaged.c
> > +++ b/mm/khugepaged.c
> > @@ -1499,6 +1499,7 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
> > collapse_address = address + offset * PAGE_SIZE;
> > ret = collapse_huge_page(mm, collapse_address, referenced,
> > unmapped, cc, order);
> > +
>
> Unrelated to this patch, but not the end of the world :)
NA....h that's fine ;)
>
> --
> Cheers,
>
> David
Cheers, Lorenzo
^ permalink raw reply
* Re: [PATCH mm-unstable v19 06/14] mm/khugepaged: generalize collapse_huge_page for mTHP collapse
From: Lorenzo Stoakes @ 2026-06-05 18:15 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel,
aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe
In-Reply-To: <95390529-3a80-473c-9433-958db7a2dc6c@kernel.org>
On Fri, Jun 05, 2026 at 07:48:17PM +0200, David Hildenbrand (Arm) wrote:
> On 6/5/26 18:14, Nico Pache wrote:
> > Pass an order to collapse_huge_page to support collapsing anon memory to
> > arbitrary orders within a PMD. order indicates what mTHP size we are
> > attempting to collapse to.
> >
> > For non-PMD collapse we must leave the anon VMA write locked until after
> > we collapse the mTHP-- in the PMD case all the pages are isolated, but in
> > the mTHP case this is not true, and we must keep the lock to prevent
> > access/changes to the page tables. This can happen if the rmap walkers hit
> > a pmd_none while the PMD entry is currently unavailable due to being
> > temporarily removed during the collapse phase.
> >
> > To properly establish the page table hierarchy without violating any
> > expectations from certain architectures (e.g. MIPS), we must make sure to
> > have the PMD reinstalled before the PTEs, and hold both PTE/PMD locks
> > before calling update_mmu_cache_range() (if they are distinct locks).
> >
> > Signed-off-by: Nico Pache <npache@redhat.com>
> > ---
>
> [...]
>
> > */
> > __folio_mark_uptodate(folio);
> > - pgtable = pmd_pgtable(_pmd);
> > -
> > spin_lock(pmd_ptl);
> > - BUG_ON(!pmd_none(*pmd));
> > - pgtable_trans_huge_deposit(mm, pmd, pgtable);
> > - map_anon_folio_pmd_nopf(folio, pmd, vma, address);
> > + VM_WARN_ON_ONCE(!pmd_none(*pmd));
> > + if (is_pmd_order(order)) {
> > + pgtable = pmd_pgtable(_pmd);
> > + pgtable_trans_huge_deposit(mm, pmd, pgtable);
> > + map_anon_folio_pmd_nopf(folio, pmd, vma, pmd_addr);
> > + } else {
> > + /*
> > + * Some architectures (e.g. MIPS) walk the live page table in
> > + * their implementation. update_mmu_cache_range() must be called
> > + * with a valid page table hierarchy and the PTE lock held.
> > + * Acquire it nested inside pmd_ptl when they are distinct locks.
> > + */
> > + if (pte_ptl != pmd_ptl)
> > + spin_lock_nested(pte_ptl, SINGLE_DEPTH_NESTING);
> > + pmd_populate(mm, pmd, pmd_pgtable(_pmd));
> > + map_anon_folio_pte_nopf(folio, pte, vma, start_addr,
> > + /*uffd_wp=*/ false);
> > + if (pte_ptl != pmd_ptl)
> > + spin_unlock(pte_ptl);
> > + }
> > spin_unlock(pmd_ptl);
> >
> > folio = NULL;
> >
> > result = SCAN_SUCCEED;
> > out_up_write:
> > + if (anon_vma_locked)
> > + anon_vma_unlock_write(vma->anon_vma);
> > + if (pte)
> > + pte_unmap(pte);
>
> We re-enable some page table walkers before we unmap the PTE.
>
> We still hold the mmap lock in write mode, so nothing would currently try
> reclaiming the page table concurrently.
Reclaim uses rmap walkers though?
Oh you mean as in page table teardown, we're safe from higher level page table
teardown, but we're not safe from zap PTE page table teardown, as
CONFIG_PT_RECLAIM makes this possible on zap now.
That is RCU safe, so the unmap would keep us safe here, but now we could lose
the PTE page table.
But, only MADV_DONTNEED sets reclaim_pt = true, and that holds the VMA read lock
so we're safe.
And anyway:
MADV_DONTNEED - VMA read lock (we hold VMA write lock)
zap_vma_for_reaping() - mmap read lock
process teardown, munmap - mmap read lock
fault - vma/mmap read lock
So the vma/mmap locks save us from those.
So rmap-wise, only the i_mmap walkers remain (truncate, hole punch, et al. and
also hugetlbfs truncate/hole-punch which does its own nonsense too), but none of
those allow for reclaim_pt to happen in any case.
So yeah we're safe but we should what, reorder these 2 statements?
But yes I agree that can be a follow-up, nothing's broken AFAICT.
>
> So I guess this works right now, but we should likely rework that code later to
> either revert both statements. Or maybe we can simply unmap like we did, and
> simply remap before we call map_anon_folio_pte_nopf()? Remapping should not fail.
>
> Alternatively to an unmap+remap, I think we could also unmap earlier for PMD
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index 6de935e76ceb..ba2a2508dda6 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -1378,6 +1378,8 @@ static enum scan_result collapse_huge_page(struct
> mm_struct *mm, unsigned long s
> if (is_pmd_order(order)) {
> anon_vma_unlock_write(vma->anon_vma);
> anon_vma_locked = false;
> + pte_unmap(pte);
> + pte = NULL;
> }
>
> result = __collapse_huge_page_copy(pte, folio, pmd, _pmd,
>
> But this can also be handled later.
Yup I mean it'd be nicer to do it in one place if we can (+ impact of holding
RCU lock longer not an issue), but all this code needs rewokr anyway.
>
> We now hold an anon_vma lock a bit longer for !pmd-collapse. But there is also
> less to copy. If that bites us, we can try optimizing later.
Yeah I do worry about holding these locks longer. But we'll see.
>
>
> So after another skim, I think this patch is ready for primetime. We can address
> the things mentioned above later ... and any fallout can be fixed later, if any.
>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Yes, also from my side - after a git range-diff and looking into above, LGTM,
so:
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
Now to go look at the core algo patch :)
>
>
> --
> Cheers,
>
> David
Cheers, Lorenzo
^ permalink raw reply
* Re: [PATCH mm-unstable v19 00/14] khugepaged: add mTHP collapse support
From: David Hildenbrand (Arm) @ 2026-06-05 18:07 UTC (permalink / raw)
To: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel
Cc: aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, ljs, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe
In-Reply-To: <20260605161422.213817-1-npache@redhat.com>
On 6/5/26 18:14, Nico Pache wrote:
> The following series provides khugepaged with the capability to collapse
> anonymous memory regions to mTHPs.
>
> To achieve this we generalize the khugepaged functions to no longer depend
> on PMD_ORDER. Then during the PMD scan, we use a bitmap to track individual
> pages that are occupied (!none/zero). After the PMD scan is done, we use
> the bitmap to find the optimal mTHP sizes for the PMD range. The
> restriction on max_ptes_none is removed during the scan, to make sure we
> account for the whole PMD range in the bitmap. When no mTHP size is
> enabled, the legacy behavior of khugepaged is maintained.
>
> We currently only support max_ptes_none values of 0 or HPAGE_PMD_NR - 1
> (ie 511). If any other value is specified, the kernel will emit a warning
> and mTHP collapse will default to max_ptes_none=0. If a mTHP collapse is
> attempted, but contains swapped out, or shared pages, we don't perform
> the collapse.
> It is now also possible to collapse to mTHPs without requiring the PMD THP
> size to be enabled. These limitations are to prevent collapse "creep"
> behavior. This prevents constantly promoting mTHPs to the next available
> size, which would occur because a collapse introduces more non-zero pages
> that would satisfy the promotion condition on subsequent scans.
>
> Patch 1-2: Generalize hugepage_vma_revalidate and alloc_charge_folio
> for arbitrary orders.
> Patch 3: Rework max_ptes_* handling into helper functions
> Patch 4: Generalize __collapse_huge_page_* for mTHP support
> Patch 5: Require collapse_huge_page to enter/exit with the lock dropped
> Patch 6: Generalize collapse_huge_page for mTHP collapse
> Patch 7: Skip collapsing mTHP to smaller orders
> Patch 8-9: Add per-order mTHP statistics and tracepoints
> Patch 10: Introduce collapse_possible_orders helper functions
> Patch 11-13: Introduce bitmap and mTHP collapse support, fully enabled
> Patch 14: Documentation
>
Went through it and didn't find any blockers. Let's wait for Lorenzo's assessment.
If he also doesn't find anything major, I think we can move forward with merging
it and handle smaller things as follow-ups.
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH mm-unstable v19 11/14] mm/khugepaged: Introduce mTHP collapse support
From: David Hildenbrand (Arm) @ 2026-06-05 18:03 UTC (permalink / raw)
To: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel
Cc: aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, ljs, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe
In-Reply-To: <20260605161422.213817-12-npache@redhat.com>
On 6/5/26 18:14, Nico Pache wrote:
> Enable khugepaged to collapse to mTHP orders. This patch implements the
> main scanning logic using a bitmap to track occupied pages and the
> algorithm to find optimal collapse sizes.
>
> Previous to this patch, PMD collapse had 3 main phases, a light weight
> scanning phase (mmap_read_lock) that determines a potential PMD
> collapse, an alloc phase (mmap unlocked), then finally heavier collapse
> phase (mmap_write_lock).
>
> To enabled mTHP collapse we make the following changes:
>
> During PMD scan phase, track occupied pages in a bitmap. When mTHP
> orders are enabled, we remove the restriction of max_ptes_none during the
> scan phase to avoid missing potential mTHP collapse candidates. Once we
> have scanned the full PMD range and updated the bitmap to track occupied
> pages, we use the bitmap to find the optimal mTHP size.
>
> Implement mthp_collapse() to walk forward through the bitmap and
> determine the best eligible order for each naturally-aligned region. The
> algorithm starts at the beginning of the PMD range and, for each offset,
> tries the highest order that fits the alignment. If the number of
> occupied PTEs in that region satisfies the max_ptes_none threshold for
> that order, a collapse is attempted. On failure, the order is
> decremented and the same offset is retried at the next smaller size. Once
> the smallest enabled order is exhausted (or a collapse succeeds), the
> offset advances past the region just processed, and the next attempt
> starts at the highest order permitted by the new offset's natural
> alignment.
>
> The algorithm works as follows:
> 1) set offset=0 and order=HPAGE_PMD_ORDER
> 2) if the order is not enabled, go to step (5)
> 3) count occupied PTEs in the (offset, order) range using
> bitmap_weight_from()
> 4) if the count satisfies the max_ptes_none threshold, attempt
> collapse; on success, advance to step (6)
> 5) if a smaller enabled order exists, decrement order and retry
> from step (2) at the same offset
> 6) advance offset past the current region and compute the next
> order from the new offset's natural alignment via __ffs(offset),
> capped at HPAGE_PMD_ORDER
> 7) repeat from step (2) until the full PMD range is covered
>
> mTHP collapses reject regions containing swapped out or shared pages.
> This is because adding new entries can lead to new none pages, and these
> may lead to constant promotion into a higher order mTHP. A similar
> issue can occur with "max_ptes_none > HPAGE_PMD_NR/2" due to a collapse
> introducing at least 2x the number of pages, and on a future scan will
> satisfy the promotion condition once again. This issue is prevented via
> the collapse_max_ptes_none() function which imposes the max_ptes_none
> restrictions above.
>
> We currently only support mTHP collapse for max_ptes_none values of 0
> and HPAGE_PMD_NR - 1. resulting in the following behavior:
>
> - max_ptes_none=0: Never introduce new empty pages during collapse
> - max_ptes_none=HPAGE_PMD_NR-1: Always try collapse to the highest
> available mTHP order
>
> Any other max_ptes_none value will emit a warning and default mTHP
> collapse to max_ptes_none=0. There should be no behavior change for PMD
> collapse.
>
> Once we determine what mTHP sizes fits best in that PMD range a collapse
> is attempted. A minimum collapse order of 2 is used as this is the lowest
> order supported by anon memory as defined by THP_ORDERS_ALL_ANON.
>
> Currently madv_collapse is not supported and will only attempt PMD
> collapse.
>
> We can also remove the check for is_khugepaged inside the PMD scan as
> the collapse_max_ptes_none() function handles this logic now.
>
> Signed-off-by: Nico Pache <npache@redhat.com>
> ---
Yeah, overall much simpler and much easier to get. As discussed, we can optimize
this later to traverse enabled orders more efficiently.
> + bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE);
> memset(cc->node_load, 0, sizeof(cc->node_load));
> nodes_clear(cc->alloc_nmask);
> +
> + enabled_orders = collapse_possible_orders(vma, vma->vm_flags, tva_flags);
> +
> + /*
> + * If PMD is the only enabled order, enforce max_ptes_none, otherwise
> + * scan all pages to populate the bitmap for mTHP collapse.
> + */
I think it would have been good to mention where the check is performed for mTHP
collapse. Can be added later.
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH mm-unstable v19 14/14] Documentation: mm: update the admin guide for mTHP collapse
From: David Hildenbrand (Arm) @ 2026-06-05 17:52 UTC (permalink / raw)
To: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel
Cc: aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, ljs, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe, Bagas Sanjaya
In-Reply-To: <20260605161422.213817-15-npache@redhat.com>
On 6/5/26 18:14, Nico Pache wrote:
> Now that we can collapse to mTHPs lets update the admin guide to
> reflect these changes and provide proper guidance on how to utilize it.
>
> Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> Reviewed-by: Bagas Sanjaya <bagasdotme@gmail.com>
> Signed-off-by: Nico Pache <npache@redhat.com>
> ---
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH mm-unstable v19 12/14] mm/khugepaged: avoid unnecessary mTHP collapse attempts
From: David Hildenbrand (Arm) @ 2026-06-05 17:49 UTC (permalink / raw)
To: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel
Cc: aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, ljs, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe, Usama Arif
In-Reply-To: <20260605161422.213817-13-npache@redhat.com>
On 6/5/26 18:14, Nico Pache wrote:
> There are cases where, if an attempted collapse fails, all subsequent
> orders are guaranteed to also fail. Avoid these collapse attempts by
> bailing out early.
>
> Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> Acked-by: Usama Arif <usama.arif@linux.dev>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Nico Pache <npache@redhat.com>
> ---
> mm/khugepaged.c | 13 +++++++++++++
> 1 file changed, 13 insertions(+)
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index 430047316f43..7de92b28dd30 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -1499,6 +1499,7 @@ static enum scan_result mthp_collapse(struct mm_struct *mm,
> collapse_address = address + offset * PAGE_SIZE;
> ret = collapse_huge_page(mm, collapse_address, referenced,
> unmapped, cc, order);
> +
Unrelated to this patch, but not the end of the world :)
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH mm-unstable v19 06/14] mm/khugepaged: generalize collapse_huge_page for mTHP collapse
From: David Hildenbrand (Arm) @ 2026-06-05 17:48 UTC (permalink / raw)
To: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel
Cc: aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, ljs, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe
In-Reply-To: <20260605161422.213817-7-npache@redhat.com>
On 6/5/26 18:14, Nico Pache wrote:
> Pass an order to collapse_huge_page to support collapsing anon memory to
> arbitrary orders within a PMD. order indicates what mTHP size we are
> attempting to collapse to.
>
> For non-PMD collapse we must leave the anon VMA write locked until after
> we collapse the mTHP-- in the PMD case all the pages are isolated, but in
> the mTHP case this is not true, and we must keep the lock to prevent
> access/changes to the page tables. This can happen if the rmap walkers hit
> a pmd_none while the PMD entry is currently unavailable due to being
> temporarily removed during the collapse phase.
>
> To properly establish the page table hierarchy without violating any
> expectations from certain architectures (e.g. MIPS), we must make sure to
> have the PMD reinstalled before the PTEs, and hold both PTE/PMD locks
> before calling update_mmu_cache_range() (if they are distinct locks).
>
> Signed-off-by: Nico Pache <npache@redhat.com>
> ---
[...]
> */
> __folio_mark_uptodate(folio);
> - pgtable = pmd_pgtable(_pmd);
> -
> spin_lock(pmd_ptl);
> - BUG_ON(!pmd_none(*pmd));
> - pgtable_trans_huge_deposit(mm, pmd, pgtable);
> - map_anon_folio_pmd_nopf(folio, pmd, vma, address);
> + VM_WARN_ON_ONCE(!pmd_none(*pmd));
> + if (is_pmd_order(order)) {
> + pgtable = pmd_pgtable(_pmd);
> + pgtable_trans_huge_deposit(mm, pmd, pgtable);
> + map_anon_folio_pmd_nopf(folio, pmd, vma, pmd_addr);
> + } else {
> + /*
> + * Some architectures (e.g. MIPS) walk the live page table in
> + * their implementation. update_mmu_cache_range() must be called
> + * with a valid page table hierarchy and the PTE lock held.
> + * Acquire it nested inside pmd_ptl when they are distinct locks.
> + */
> + if (pte_ptl != pmd_ptl)
> + spin_lock_nested(pte_ptl, SINGLE_DEPTH_NESTING);
> + pmd_populate(mm, pmd, pmd_pgtable(_pmd));
> + map_anon_folio_pte_nopf(folio, pte, vma, start_addr,
> + /*uffd_wp=*/ false);
> + if (pte_ptl != pmd_ptl)
> + spin_unlock(pte_ptl);
> + }
> spin_unlock(pmd_ptl);
>
> folio = NULL;
>
> result = SCAN_SUCCEED;
> out_up_write:
> + if (anon_vma_locked)
> + anon_vma_unlock_write(vma->anon_vma);
> + if (pte)
> + pte_unmap(pte);
We re-enable some page table walkers before we unmap the PTE.
We still hold the mmap lock in write mode, so nothing would currently try
reclaiming the page table concurrently.
So I guess this works right now, but we should likely rework that code later to
either revert both statements. Or maybe we can simply unmap like we did, and
simply remap before we call map_anon_folio_pte_nopf()? Remapping should not fail.
Alternatively to an unmap+remap, I think we could also unmap earlier for PMD
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 6de935e76ceb..ba2a2508dda6 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -1378,6 +1378,8 @@ static enum scan_result collapse_huge_page(struct
mm_struct *mm, unsigned long s
if (is_pmd_order(order)) {
anon_vma_unlock_write(vma->anon_vma);
anon_vma_locked = false;
+ pte_unmap(pte);
+ pte = NULL;
}
result = __collapse_huge_page_copy(pte, folio, pmd, _pmd,
But this can also be handled later.
We now hold an anon_vma lock a bit longer for !pmd-collapse. But there is also
less to copy. If that bites us, we can try optimizing later.
So after another skim, I think this patch is ready for primetime. We can address
the things mentioned above later ... and any fallout can be fixed later, if any.
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply related
* Re: [PATCH mm-unstable v19 10/14] mm/khugepaged: introduce collapse_possible_orders helper functions
From: Lorenzo Stoakes @ 2026-06-05 17:46 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
akpm, anshuman.khandual, apopple, baohua, baolin.wang, byungchul,
catalin.marinas, cl, corbet, dave.hansen, david, dev.jain, gourry,
hannes, hughd, jack, jackmanb, jannh, jglisse, joshua.hahnjy, kas,
lance.yang, liam, mathieu.desnoyers, matthew.brost, mhiramat,
mhocko, peterx, pfalcato, rakie.kim, raquini, rdunlap,
richard.weiyang, rientjes, rostedt, rppt, ryan.roberts, shivankg,
sunnanyong, surenb, thomas.hellstrom, tiwai, usamaarif642, vbabka,
vishal.moola, wangkefeng.wang, will, willy, yang, ying.huang, ziy,
zokeefe
In-Reply-To: <20260605161422.213817-11-npache@redhat.com>
On Fri, Jun 05, 2026 at 10:14:17AM -0600, Nico Pache wrote:
> Add collapse_possible_orders() to generalize THP order eligibility. The
> function determines which THP orders are permitted based on collapse
> context (khugepaged vs madv_collapse). We also add collapse_possible()
> as a thin wrapper around collapse_possible_orders() that returns a bool
> rather than the whole bitmap.
>
> This consolidates collapse configuration logic and provides a clean
> interface for future mTHP collapse support where the orders may be
> different.
>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
> Signed-off-by: Nico Pache <npache@redhat.com>
LGTM, so:
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> mm/khugepaged.c | 24 +++++++++++++++++++++---
> 1 file changed, 21 insertions(+), 3 deletions(-)
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index 26c343a6fa3d..ec886a031952 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -554,12 +554,30 @@ void __khugepaged_enter(struct mm_struct *mm)
> wake_up_interruptible(&khugepaged_wait);
> }
>
> +/*
> + * Check what orders are possible based on the vma and collapse type.
> + * This is used to determine if mTHP collapse is a viable option.
> + */
> +static unsigned long collapse_possible_orders(struct vm_area_struct *vma,
> + vm_flags_t vm_flags, enum tva_type tva_flags)
> +{
> + const unsigned long orders = BIT(HPAGE_PMD_ORDER);
> +
> + return thp_vma_allowable_orders(vma, vm_flags, tva_flags, orders);
> +}
> +
> +static bool collapse_possible(struct vm_area_struct *vma,
> + vm_flags_t vm_flags, enum tva_type tva_flags)
> +{
> + return collapse_possible_orders(vma, vm_flags, tva_flags);
> +}
> +
> void khugepaged_enter_vma(struct vm_area_struct *vma,
> vm_flags_t vm_flags)
> {
> if (!mm_flags_test(MMF_VM_HUGEPAGE, vma->vm_mm) &&
> hugepage_pmd_enabled()) {
> - if (thp_vma_allowable_order(vma, vm_flags, TVA_KHUGEPAGED, PMD_ORDER))
> + if (collapse_possible(vma, vm_flags, TVA_KHUGEPAGED))
> __khugepaged_enter(vma->vm_mm);
> }
> }
> @@ -2700,7 +2718,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
> cc->progress++;
> break;
> }
> - if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_KHUGEPAGED, PMD_ORDER)) {
> + if (!collapse_possible(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
> cc->progress++;
> continue;
> }
> @@ -3010,7 +3028,7 @@ int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
> BUG_ON(vma->vm_start > start);
> BUG_ON(vma->vm_end < end);
>
> - if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_FORCED_COLLAPSE, PMD_ORDER))
> + if (!collapse_possible(vma, vma->vm_flags, TVA_FORCED_COLLAPSE))
> return -EINVAL;
>
> cc = kmalloc_obj(*cc);
> --
> 2.54.0
>
^ permalink raw reply
* [PATCH v2 18/18] Documentation/kernel-parameters: Add trace_remote
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
The trace_remote parameter allows to configure a trace remote on
registration. The syntax is similar to trace_instance.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 97007f4f69d4..d379fec5e81c 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -7792,6 +7792,25 @@ Kernel parameters
See also Documentation/trace/ftrace.rst "trace options"
section.
+ trace_remote=[remote-info]
+ [FTRACE] Configure a trace remote instance at boot.
+ Format: <name>[^option1[^option2...]][,event1[,event2...]]
+
+ Supported options:
+
+ dump_on_panic - Enable dumping the trace buffer on
+ panic.
+ dmesg - Redirect tracing output to dmesg.
+ buf_size=<size> - Set the trace buffer size (e.g. 2M).
+ poll=<ms> - Set the trace remote polling interval
+ in milliseconds.
+
+ Events are a comma-separated list of events to enable.
+ If events are specified, tracing is automatically enabled.
+
+ Multiple remotes can be configured by specifying this
+ parameter multiple times.
+
trace_trigger=[trigger-list]
[FTRACE] Add an event trigger on specific events.
Set a trigger on top of a specific event, with an optional
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 17/18] Documentation: tracing/remotes: Add detailed tracefs layout
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
Add a description for each tracefs file available in a trace remote
instance.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/Documentation/trace/remotes.rst b/Documentation/trace/remotes.rst
index 1f9d764f69aa..b02ebed4a03f 100644
--- a/Documentation/trace/remotes.rst
+++ b/Documentation/trace/remotes.rst
@@ -19,8 +19,8 @@ for which the host kernel can see and expose to user space.
Register a remote
=================
-A remote must provide a set of callbacks `struct trace_remote_callbacks` whom
-description can be found below. Those callbacks allows Tracefs to enable and
+A remote must provide a set of callbacks `struct trace_remote_callbacks` whose
+description can be found below. Those callbacks allow Tracefs to enable and
disable tracing and events, to load and unload a tracing buffer (a set of
ring-buffers) and to swap a reader page with the head page, which enables
consuming reading.
@@ -28,8 +28,66 @@ consuming reading.
.. kernel-doc:: include/linux/trace_remote.h
Once registered, an instance will appear for this remote in the Tracefs
-directory **remotes/**. Buffers can then be read using the usual Tracefs files
-**trace_pipe** and **trace**.
+directory **remotes/**. The files within this directory allow configuring
+and reading the remote buffer (see `The File System` below).
+
+The File System
+===============
+A remote tracing instance is represented by a directory in Tracefs under
+**remotes/**. The layout and files within it are very similar to standard ftrace
+instances. Inside the remote directory, the following files and directories are
+available:
+
+ tracing_on
+ This file allows enabling or disabling the remote tracing.
+
+ buffer_size_kb
+ This file displays and allows changing the size of the per-CPU ring
+ buffers used by the remote. It also shows if the buffer is **loaded** or
+ **unloaded**. To change the size, the remote buffers must be unloaded
+ first. Remote buffers are automatically unloaded when **tracing_on** is
+ off, no one is reading the buffer (either by accessing **trace_pipe** or
+ when **dmesg** is on) and no events remain in the buffer.
+
+ trace
+ Display the human-readable content of the remote buffers. Reading this
+ file is non-consuming. Writing to this file clears the ring buffers.
+
+ trace_pipe
+ Similar to **trace** but reading it consumes the events from the ring
+ buffers (consuming read). It blocks if there are no new events.
+
+ dmesg
+ When enabled, all events from the remote are redirected to the kernel
+ dmesg. This is similar to the **tp_printk** option for in-kernel events.
+ It counts as a reader of the remote buffers and prevents unloading.
+
+ dump_on_panic
+ When enabled, the remote tracing buffer is dumped to the console when a
+ kernel panic occurs.
+
+ poll_ms
+ Modifies the polling interval for the trace_remote.
+
+ per_cpu/
+ This directory contains subdirectories for each possible CPU (e.g.,
+ **cpu0/**, **cpu1/** ...)
+
+ per_cpu/cpuX/trace
+ This is similar to the **trace** file, but it will only display the data
+ specific for the CPU. If written to, it only clears the specific CPU
+ buffer.
+
+ per_cpu/cpuX/trace_pipe
+ This is similar to the **trace_pipe** file, and is a consuming read, but
+ it will only display (and consume) the data specific to the CPU.
+
+ events/
+ This directory contains remote events that can be enabled or disabled.
+
+ events/enable
+ Allows enabling or disabling all the remote events.
+
Declare a remote event
======================
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 16/18] tracing/remotes: Add trace_remote cmdline options
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
Following the same format as trace_instance, add a cmdline to configure
a trace remote on registration:
trace_remote=<remote>^<opt1>^<opt2>,<evt1>,<evt2>
Enabling events automatically turns on tracing.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index f72fc862ae7f..a0a372c47562 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -13,6 +13,8 @@
#include <linux/trace_seq.h>
#include <linux/types.h>
+#include <asm/setup.h>
+
#include "trace.h"
#define TRACEFS_DIR "remotes"
@@ -1167,6 +1169,132 @@ static int poll_ms_show(struct seq_file *s, void *unused)
}
DEFINE_TRACE_REMOTE_ATTRIBUTE(poll_ms);
+static char trace_remote_cmdline[COMMAND_LINE_SIZE];
+
+static int __init set_trace_remote_cmdline(char *str)
+{
+ static int idx;
+
+ if (!str)
+ return 0;
+
+ strscpy(trace_remote_cmdline + idx, str, COMMAND_LINE_SIZE - idx);
+ idx += strlen(str);
+ trace_remote_cmdline[idx++] = '\t';
+ return 1;
+}
+__setup("trace_remote=", set_trace_remote_cmdline);
+
+static void trace_remote_apply_cmdline_opts(struct trace_remote *remote, char *cmdline)
+{
+ bool dmesg_on = false;
+ char *opt;
+ int ret;
+
+ while ((opt = strsep(&cmdline, "^"))) {
+ if (!*opt)
+ continue;
+
+ if (!strcmp(opt, "dump_on_panic")) {
+ remote->panic_on = true;
+ } else if (!strcmp(opt, "dmesg")) {
+ dmesg_on = true;
+ } else if (!strncmp(opt, "buf_size=", 9)) {
+ /* buf_size can only be applied if the buffer is unloaded */
+ if (!WARN_ON(trace_remote_loaded(remote)))
+ remote->trace_buffer_size = memparse(opt + 9, NULL);
+ } else if (!strncmp(opt, "poll=", 5)) {
+ unsigned int poll_ms;
+
+ if (!kstrtouint(opt + 5, 10, &poll_ms) && poll_ms > 0)
+ remote->poll_ms = poll_ms;
+ else
+ pr_warn("Invalid trace remote poll '%s'\n", opt);
+ } else {
+ pr_warn("Unknown trace remote option '%s'\n", opt);
+ }
+ }
+
+ if (dmesg_on) {
+ ret = trace_remote_enable_dmesg(remote, true);
+ if (ret)
+ pr_warn("Failed to enable trace remote dmesg (%d)\n", ret);
+ }
+}
+
+static struct remote_event *
+trace_remote_find_event_by_name(struct trace_remote *remote, const char *name);
+
+static int
+trace_remote_enable_event(struct trace_remote *remote, struct remote_event *evt, bool enable);
+
+static void trace_remote_apply_cmdline_events(struct trace_remote *remote, char *cmdline)
+{
+ bool tracing_on = false;
+ char *token;
+ int ret;
+
+ while ((token = strsep(&cmdline, ","))) {
+ struct remote_event *evt;
+
+ if (!*token)
+ continue;
+
+ evt = trace_remote_find_event_by_name(remote, token);
+ if (!evt) {
+ pr_warn("trace remote event '%s' not found\n", token);
+ continue;
+ }
+
+ ret = trace_remote_enable_event(remote, evt, true);
+ if (ret)
+ pr_warn("Failed to enable trace remote event '%s' (%d)\n", token, ret);
+ else
+ tracing_on = true;
+ }
+
+ if (tracing_on) {
+ ret = trace_remote_enable_tracing(remote);
+ if (ret)
+ pr_warn("Failed to enable trace remote tracing (%d)\n", ret);
+ }
+}
+
+static void trace_remote_apply_cmdline(const char *name, struct trace_remote *remote)
+{
+ char *cmdline __free(kfree) = NULL;
+ char *events_cmdline = NULL;
+ char *opts_cmdline = NULL;
+ char *curr, *next;
+
+ if (!trace_remote_cmdline[0])
+ return;
+
+ cmdline = kstrdup(trace_remote_cmdline, GFP_KERNEL);
+ if (!cmdline)
+ return;
+
+ next = cmdline;
+ while ((curr = strsep(&next, "\t"))) {
+ char *token = strsep(&curr, ",");
+ char *rname = strsep(&token, "^");
+
+ if (strcmp(rname, name) == 0) {
+ opts_cmdline = token;
+ events_cmdline = curr;
+ break;
+ }
+ }
+
+ guard(mutex)(&remote->lock);
+
+ if (opts_cmdline)
+ trace_remote_apply_cmdline_opts(remote, opts_cmdline);
+ if (events_cmdline)
+ trace_remote_apply_cmdline_events(remote, events_cmdline);
+}
+
+
static int trace_remote_init_tracefs(const char *name, struct trace_remote *remote)
{
struct dentry *remote_d, *percpu_d, *d;
@@ -1343,6 +1471,7 @@ int trace_remote_register(const char *name, struct trace_remote_callbacks *cbs,
}
list_add(&remote->node, &trace_remotes);
+ trace_remote_apply_cmdline(name, remote);
retain_and_null_ptr(remote);
return 0;
@@ -1822,3 +1951,15 @@ static struct remote_event *trace_remote_find_event(struct trace_remote *remote,
return bsearch((const void *)(unsigned long)id, remote->events, remote->nr_events,
sizeof(*remote->events), __cmp_events);
}
+
+static struct remote_event *
+trace_remote_find_event_by_name(struct trace_remote *remote, const char *name)
+{
+ int i;
+
+ for (i = 0; i < remote->nr_events; i++) {
+ if (!strcmp(remote->events[i].name, name))
+ return &remote->events[i];
+ }
+ return NULL;
+}
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 15/18] tracing/remotes: Add poll_ms tracefs file
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
Add a tracefs file to configure the trace remote polling period. Keep
the default value to 100ms.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index cf99752e1cd5..f72fc862ae7f 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -1133,6 +1133,40 @@ static int dump_on_panic_show(struct seq_file *s, void *unused)
}
DEFINE_TRACE_REMOTE_ATTRIBUTE(dump_on_panic);
+static ssize_t poll_ms_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
+{
+ struct seq_file *seq = filp->private_data;
+ struct trace_remote *remote = seq->private;
+ unsigned int val;
+ int ret;
+
+ ret = kstrtouint_from_user(ubuf, cnt, 10, &val);
+ if (ret)
+ return ret;
+
+ if (!val)
+ return -EINVAL;
+
+ guard(mutex)(&remote->lock);
+
+ if (val < remote->poll_ms && remote->poll_cnt)
+ mod_delayed_work(system_percpu_wq, &remote->poll_work, msecs_to_jiffies(val));
+
+ remote->poll_ms = val;
+
+ return cnt;
+}
+
+static int poll_ms_show(struct seq_file *s, void *unused)
+{
+ struct trace_remote *remote = s->private;
+
+ seq_printf(s, "%u\n", remote->poll_ms);
+
+ return 0;
+}
+DEFINE_TRACE_REMOTE_ATTRIBUTE(poll_ms);
+
static int trace_remote_init_tracefs(const char *name, struct trace_remote *remote)
{
struct dentry *remote_d, *percpu_d, *d;
@@ -1165,6 +1199,10 @@ static int trace_remote_init_tracefs(const char *name, struct trace_remote *remo
if (!d)
goto err;
+ d = trace_create_file("poll_ms", TRACEFS_MODE_WRITE, remote_d, remote, &poll_ms_fops);
+ if (!d)
+ goto err;
+
d = trace_create_file("buffer_size_kb", TRACEFS_MODE_WRITE, remote_d, remote,
&buffer_size_kb_fops);
if (!d)
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 14/18] tracing/remotes: selftests: Add a test for the dump_on_panic tracefs file
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
Exercise the newly introduced dump_on_panic tracefs file that turns on
or off the trace remote buffer dump on system panic.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/dump_on_panic.tc b/tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/dump_on_panic.tc
new file mode 100644
index 000000000000..5e3d3c412ecd
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/dump_on_panic.tc
@@ -0,0 +1,11 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: Test hypervisor trace dump_on_panic
+# requires: remotes/hypervisor/write_event
+
+SOURCE_REMOTE_TEST=1
+. $TEST_DIR/remotes/dump_on_panic.tc
+
+set -e
+setup_remote "hypervisor"
+test_dump_on_panic
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/dump_on_panic.tc b/tools/testing/selftests/ftrace/test.d/remotes/dump_on_panic.tc
new file mode 100644
index 000000000000..defc6f3a07ca
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/remotes/dump_on_panic.tc
@@ -0,0 +1,51 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: Test trace remote dump_on_panic
+# requires: remotes/test
+
+. $TEST_DIR/remotes/functions
+
+test_dump_on_panic()
+{
+ #
+ # Toggle when the buffer is unloaded
+ #
+ echo 1 > dump_on_panic
+ echo 0 > dump_on_panic
+
+ #
+ # Toggle when the buffer is loaded
+ #
+ echo 1 > tracing_on
+ assert_loaded
+
+ echo 1 > dump_on_panic
+ echo 0 > dump_on_panic
+
+ #
+ # Load and unload buffer while dump_on_panic is enabled
+ #
+ echo 0 > tracing_on
+ assert_unloaded
+
+ echo 1 > dump_on_panic
+ echo 1 > tracing_on
+ echo 0 > tracing_on
+
+ # REMOVE ME FOR A PROPER OOPS TEST
+ return
+
+ echo 1 > tracing_on
+
+ for i in $(seq 1 32); do
+ echo $i > write_event
+ done
+
+ echo c > /proc/sysrq-trigger
+}
+
+if [ -z "$SOURCE_REMOTE_TEST" ]; then
+ set -e
+ setup_remote_test
+ test_dump_on_panic
+fi
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/functions b/tools/testing/selftests/ftrace/test.d/remotes/functions
index 4a14aa72fdf0..bdd28b5b8596 100644
--- a/tools/testing/selftests/ftrace/test.d/remotes/functions
+++ b/tools/testing/selftests/ftrace/test.d/remotes/functions
@@ -9,6 +9,7 @@ setup_remote()
cd remotes/$name/
echo 0 > tracing_on
echo 0 > dmesg
+ echo 0 > dump_on_panic
clear_trace
echo 7 > buffer_size_kb
echo 0 > events/enable
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 13/18] tracing/remotes: Add dump_on_panic tracefs file
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
When enabled, dump_on_panic will dump the content of the trace remote
buffer if the system panics.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index b0404ad8981d..cf99752e1cd5 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -7,6 +7,7 @@
#include <linux/kstrtox.h>
#include <linux/lockdep.h>
#include <linux/mutex.h>
+#include <linux/panic_notifier.h>
#include <linux/tracefs.h>
#include <linux/trace_remote.h>
#include <linux/trace_seq.h>
@@ -22,6 +23,7 @@ enum tri_type {
TRI_CONSUMING,
TRI_NONCONSUMING,
TRI_DMESG,
+ TRI_PANIC,
};
struct trace_remote_iterator {
@@ -60,6 +62,9 @@ struct trace_remote {
struct delayed_work poll_work;
unsigned int poll_cnt;
bool tracing_on;
+ bool panic_on;
+ struct notifier_block panic_notifier;
+ struct trace_remote_iterator *panic_iter;
};
static DEFINE_MUTEX(trace_remotes_lock);
@@ -71,10 +76,15 @@ static bool trace_remote_loaded(struct trace_remote *remote)
return !!remote->trace_buffer;
}
+static void trace_remote_unload(struct trace_remote *remote);
+static int trace_remote_panic_load(struct trace_remote *remote);
+static void trace_remote_panic_unload(struct trace_remote *remote);
+
static int trace_remote_load(struct trace_remote *remote)
{
struct ring_buffer_remote *rb_remote = &remote->rb_remote;
struct trace_buffer_desc *desc;
+ int ret = 0;
lockdep_assert_held(&remote->lock);
@@ -89,15 +99,28 @@ static int trace_remote_load(struct trace_remote *remote)
rb_remote->swap_reader_page = remote->cbs->swap_reader_page;
rb_remote->priv = remote->priv;
rb_remote->reset = remote->cbs->reset;
+ remote->trace_buffer_desc = desc;
remote->trace_buffer = ring_buffer_alloc_remote(rb_remote);
if (!remote->trace_buffer) {
remote->cbs->unload_trace_buffer(desc, remote->priv);
return -ENOMEM;
}
- remote->trace_buffer_desc = desc;
+ if (remote->panic_on) {
+ ret = trace_remote_panic_load(remote);
+ if (ret)
+ trace_remote_unload(remote);
+ }
- return 0;
+ return ret;
+}
+
+static void trace_remote_unload(struct trace_remote *remote)
+{
+ trace_remote_panic_unload(remote);
+ ring_buffer_free(remote->trace_buffer);
+ remote->trace_buffer = NULL;
+ remote->cbs->unload_trace_buffer(remote->trace_buffer_desc, remote->priv);
}
static void trace_remote_try_unload(struct trace_remote *remote)
@@ -115,9 +138,7 @@ static void trace_remote_try_unload(struct trace_remote *remote)
if (!ring_buffer_empty(remote->trace_buffer))
return;
- ring_buffer_free(remote->trace_buffer);
- remote->trace_buffer = NULL;
- remote->cbs->unload_trace_buffer(remote->trace_buffer_desc, remote->priv);
+ trace_remote_unload(remote);
}
static int trace_remote_enable_tracing(struct trace_remote *remote)
@@ -434,58 +455,68 @@ static void trace_remote_dec_poll(struct trace_remote *remote)
static struct trace_remote_iterator
*trace_remote_iter(struct trace_remote *remote, int cpu, enum tri_type type)
{
- struct trace_remote_iterator *iter = NULL;
+ struct trace_remote_iterator *iter __free(kfree) = kzalloc_obj(*iter);
int ret;
lockdep_assert_held(&remote->lock);
- if (type == TRI_NONCONSUMING && !trace_remote_loaded(remote))
- return NULL;
+ if (!iter)
+ return ERR_PTR(-ENOMEM);
- ret = trace_remote_get(remote, cpu);
- if (ret)
- return ERR_PTR(ret);
+ switch (type) {
+ case TRI_NONCONSUMING:
+ if (!trace_remote_loaded(remote))
+ return NULL;
+ fallthrough;
+ case TRI_CONSUMING:
+ case TRI_DMESG:
+ ret = trace_remote_get(remote, cpu);
+ if (ret)
+ return ERR_PTR(ret);
+ break;
+ case TRI_PANIC:
+ break;
+ }
if (!trace_remote_has_cpu(remote, cpu)) {
ret = -ENODEV;
goto err;
}
- iter = kzalloc_obj(*iter);
- if (iter) {
- iter->remote = remote;
- iter->cpu = cpu;
- iter->type = type;
- trace_seq_init(&iter->seq);
+ iter->remote = remote;
+ iter->cpu = cpu;
+ iter->type = type;
+ trace_seq_init(&iter->seq);
- switch (type) {
- case TRI_DMESG:
- /* only one printk iter allowed */
- if (WARN_ON_ONCE(remote->dmesg)) {
- ret = -EBUSY;
- break;
- }
- smp_store_release(&remote->dmesg, iter);
- fallthrough;
- case TRI_CONSUMING:
- trace_remote_inc_poll(remote);
- break;
- case TRI_NONCONSUMING:
- ret = __alloc_ring_buffer_iter(iter, cpu);
- break;
+ switch (type) {
+ case TRI_DMESG:
+ /* only one dmesg iter allowed */
+ if (WARN_ON_ONCE(remote->dmesg)) {
+ ret = -EBUSY;
+ goto err;
}
-
+ smp_store_release(&remote->dmesg, iter);
+ fallthrough;
+ case TRI_CONSUMING:
+ trace_remote_inc_poll(remote);
+ break;
+ case TRI_PANIC:
+ case TRI_NONCONSUMING:
+ ret = __alloc_ring_buffer_iter(iter, cpu);
if (ret)
goto err;
-
- return iter;
+ break;
}
- ret = -ENOMEM;
-err:
- kfree(iter);
- trace_remote_put(remote);
+ return no_free_ptr(iter);
+err:
+ switch (type) {
+ case TRI_PANIC:
+ break;
+ default:
+ trace_remote_put(remote);
+ }
return ERR_PTR(ret);
}
@@ -508,14 +539,18 @@ static void trace_remote_iter_free(struct trace_remote_iterator *iter)
fallthrough;
case TRI_CONSUMING:
trace_remote_dec_poll(remote);
+ trace_remote_put(remote);
break;
case TRI_NONCONSUMING:
+ trace_remote_put(remote);
+ __free_ring_buffer_iter(iter, iter->cpu);
+ break;
+ case TRI_PANIC:
__free_ring_buffer_iter(iter, iter->cpu);
break;
}
kfree(iter);
- trace_remote_put(remote);
}
static bool trace_remote_iter_is_consuming(struct trace_remote_iterator *iter)
@@ -988,6 +1023,116 @@ static int dmesg_show(struct seq_file *s, void *unused)
}
DEFINE_TRACE_REMOTE_ATTRIBUTE(dmesg);
+static int trace_remote_panic_handler(struct notifier_block *self, unsigned long ev, void *v)
+{
+ struct trace_remote *remote = container_of(self, struct trace_remote, panic_notifier);
+ struct trace_remote_iterator *iter = smp_load_acquire(&remote->panic_iter);
+ int cpu;
+
+ if (!iter) {
+ pr_warn("Unexpected error: no panic iterator for the trace remote\n");
+ return NOTIFY_DONE;
+ }
+
+ for_each_possible_cpu(cpu) {
+ if (iter->rb_iters[cpu]) {
+ /* No RING_BUFFER_ALL_CPUS to avoid taking cpu_read_lock() */
+ ring_buffer_read_remote_meta_page(remote->trace_buffer, cpu);
+ ring_buffer_iter_reset(iter->rb_iters[cpu]);
+ }
+ }
+
+ while (trace_remote_iter_read_event(iter)) {
+ trace_seq_init(&iter->seq);
+
+ trace_remote_iter_print_event(iter);
+ pr_emerg("%s", iter->seq.buffer);
+
+ trace_remote_iter_move(iter);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int trace_remote_panic_load(struct trace_remote *remote)
+{
+ struct notifier_block *notifier = &remote->panic_notifier;
+ struct trace_remote_iterator *iter;
+
+ lockdep_assert_held(&remote->lock);
+
+ if (remote->panic_iter)
+ return 0;
+
+ iter = trace_remote_iter(remote, RING_BUFFER_ALL_CPUS, TRI_PANIC);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ smp_store_release(&remote->panic_iter, iter);
+
+ notifier->notifier_call = trace_remote_panic_handler;
+ notifier->priority = INT_MAX - 1;
+ atomic_notifier_chain_register(&panic_notifier_list, notifier);
+
+ return 0;
+}
+
+static void trace_remote_panic_unload(struct trace_remote *remote)
+{
+ struct trace_remote_iterator *iter = remote->panic_iter;
+
+ lockdep_assert_held(&remote->lock);
+
+ if (!iter)
+ return;
+
+ atomic_notifier_chain_unregister(&panic_notifier_list, &remote->panic_notifier);
+ smp_store_release(&remote->panic_iter, NULL);
+ trace_remote_iter_free(iter);
+}
+
+static ssize_t dump_on_panic_write(struct file *filp, const char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ struct seq_file *seq = filp->private_data;
+ struct trace_remote *remote = seq->private;
+ bool enable;
+ int ret;
+
+ ret = kstrtobool_from_user(ubuf, cnt, &enable);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&remote->lock);
+
+ if (enable == remote->panic_on)
+ return cnt;
+
+ if (trace_remote_loaded(remote)) {
+ if (enable) {
+ ret = trace_remote_panic_load(remote);
+ if (ret)
+ return ret;
+ } else {
+ trace_remote_panic_unload(remote);
+ }
+ }
+
+ remote->panic_on = enable;
+
+ return cnt;
+}
+
+static int dump_on_panic_show(struct seq_file *s, void *unused)
+{
+ struct trace_remote *remote = s->private;
+
+ seq_printf(s, "%d\n", remote->panic_on);
+
+ return 0;
+}
+DEFINE_TRACE_REMOTE_ATTRIBUTE(dump_on_panic);
+
static int trace_remote_init_tracefs(const char *name, struct trace_remote *remote)
{
struct dentry *remote_d, *percpu_d, *d;
@@ -1015,6 +1160,11 @@ static int trace_remote_init_tracefs(const char *name, struct trace_remote *remo
if (!d)
goto err;
+ d = trace_create_file("dump_on_panic", TRACEFS_MODE_WRITE, remote_d, remote,
+ &dump_on_panic_fops);
+ if (!d)
+ goto err;
+
d = trace_create_file("buffer_size_kb", TRACEFS_MODE_WRITE, remote_d, remote,
&buffer_size_kb_fops);
if (!d)
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 12/18] ring-buffer: Add kerneldoc for ring_buffer_poll_remote
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
Document ring_buffer_poll_remote().
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c
index efae14e3ba80..2f99f77a039b 100644
--- a/kernel/trace/ring_buffer.c
+++ b/kernel/trace/ring_buffer.c
@@ -6676,6 +6676,17 @@ int ring_buffer_read_remote_meta_page(struct trace_buffer *buffer, int cpu)
return 0;
}
+/**
+ * ring_buffer_poll_remote - poll a remote ring buffer for new data
+ * @buffer: The ring buffer
+ * @cpu: The CPU buffer to poll (or RING_BUFFER_ALL_CPUS)
+ *
+ * This function polls the specified remote CPU buffer (or all of them)
+ * by reading its meta page to update the local reader's view. If new
+ * entries are detected, it triggers wakeups for any waiting readers.
+ * Returns:
+ * 0 on success, or -EINVAL if the CPU is not in the buffer's cpumask.
+ */
int ring_buffer_poll_remote(struct trace_buffer *buffer, int cpu)
{
struct ring_buffer_per_cpu *cpu_buffer;
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 11/18] ring-buffer: Add ring_buffer_read_remote_meta_page()
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
In preparation for the introduction of a panic handler for trace
remotes, add a ring_buffer_read_remote_meta_page(). This is basically
similar to ring_buffer_poll_remote, but it doesn't try to wake-up
readers and, in the !RING_BUFFER_ALL_CPUS case, uses panic-friendly
locks.
While at it, update trace_remote_has_cpu() to use this new function
instead of ring_buffer_poll_remote(), avoiding unnecessary wakeups when
verifying if a CPU buffer is active.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h
index 994f52b34344..6e008a548063 100644
--- a/include/linux/ring_buffer.h
+++ b/include/linux/ring_buffer.h
@@ -298,6 +298,7 @@ struct ring_buffer_remote {
void *priv;
};
+int ring_buffer_read_remote_meta_page(struct trace_buffer *buffer, int cpu);
int ring_buffer_poll_remote(struct trace_buffer *buffer, int cpu);
struct trace_buffer *
diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c
index 88ef44e2da53..efae14e3ba80 100644
--- a/kernel/trace/ring_buffer.c
+++ b/kernel/trace/ring_buffer.c
@@ -6635,6 +6635,47 @@ bool ring_buffer_empty_cpu(struct trace_buffer *buffer, int cpu)
}
EXPORT_SYMBOL_GPL(ring_buffer_empty_cpu);
+/**
+ * ring_buffer_read_remote_meta_page - read the meta page of a remote ring buffer
+ * @buffer: The ring buffer
+ * @cpu: The CPU buffer to read (or RING_BUFFER_ALL_CPUS)
+ *
+ * Returns:
+ * 0 on success, or -EINVAL if the CPU is not in the buffer's cpumask.
+ */
+int ring_buffer_read_remote_meta_page(struct trace_buffer *buffer, int cpu)
+{
+ struct ring_buffer_per_cpu *cpu_buffer;
+
+ if (cpu != RING_BUFFER_ALL_CPUS) {
+ unsigned long flags;
+ bool dolock;
+
+ if (!cpumask_test_cpu(cpu, buffer->cpumask))
+ return -EINVAL;
+
+ cpu_buffer = buffer->buffers[cpu];
+
+ local_irq_save(flags);
+ dolock = rb_reader_lock(cpu_buffer);
+ rb_read_remote_meta_page(cpu_buffer);
+ rb_reader_unlock(cpu_buffer, dolock);
+ local_irq_restore(flags);
+ return 0;
+ }
+
+ guard(cpus_read_lock)();
+
+ for_each_buffer_cpu(buffer, cpu) {
+ cpu_buffer = buffer->buffers[cpu];
+
+ guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock);
+ rb_read_remote_meta_page(cpu_buffer);
+ }
+
+ return 0;
+}
+
int ring_buffer_poll_remote(struct trace_buffer *buffer, int cpu)
{
struct ring_buffer_per_cpu *cpu_buffer;
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index 19dfa355b7f3..b0404ad8981d 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -350,7 +350,7 @@ static bool trace_remote_has_cpu(struct trace_remote *remote, int cpu)
if (cpu == RING_BUFFER_ALL_CPUS)
return true;
- return ring_buffer_poll_remote(remote->trace_buffer, cpu) == 0;
+ return ring_buffer_read_remote_meta_page(remote->trace_buffer, cpu) == 0;
}
static void __free_ring_buffer_iter(struct trace_remote_iterator *iter, int cpu)
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 10/18] ring-buffer: Use panic-friendly locking in ring_buffer_iter interface
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
In preparation for allowing trace_remote to dump the buffer on panic,
make the non-consuming iterator functions panic-friendly.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c
index 183326633037..88ef44e2da53 100644
--- a/kernel/trace/ring_buffer.c
+++ b/kernel/trace/ring_buffer.c
@@ -5444,6 +5444,9 @@ static void rb_iter_reset(struct ring_buffer_iter *iter)
}
}
+static inline bool rb_reader_lock(struct ring_buffer_per_cpu *cpu_buffer);
+static inline void rb_reader_unlock(struct ring_buffer_per_cpu *cpu_buffer, bool locked);
+
/**
* ring_buffer_iter_reset - reset an iterator
* @iter: The iterator to reset
@@ -5455,15 +5458,18 @@ void ring_buffer_iter_reset(struct ring_buffer_iter *iter)
{
struct ring_buffer_per_cpu *cpu_buffer;
unsigned long flags;
+ bool dolock;
if (!iter)
return;
cpu_buffer = iter->cpu_buffer;
- raw_spin_lock_irqsave(&cpu_buffer->reader_lock, flags);
+ local_irq_save(flags);
+ dolock = rb_reader_lock(cpu_buffer);
rb_iter_reset(iter);
- raw_spin_unlock_irqrestore(&cpu_buffer->reader_lock, flags);
+ rb_reader_unlock(cpu_buffer, dolock);
+ local_irq_restore(flags);
}
EXPORT_SYMBOL_GPL(ring_buffer_iter_reset);
@@ -6127,11 +6133,14 @@ ring_buffer_iter_peek(struct ring_buffer_iter *iter, u64 *ts)
struct ring_buffer_per_cpu *cpu_buffer = iter->cpu_buffer;
struct ring_buffer_event *event;
unsigned long flags;
+ bool dolock;
again:
- raw_spin_lock_irqsave(&cpu_buffer->reader_lock, flags);
+ local_irq_save(flags);
+ dolock = rb_reader_lock(cpu_buffer);
event = rb_iter_peek(iter, ts);
- raw_spin_unlock_irqrestore(&cpu_buffer->reader_lock, flags);
+ rb_reader_unlock(cpu_buffer, dolock);
+ local_irq_restore(flags);
if (event && event->type_len == RINGBUF_TYPE_PADDING)
goto again;
@@ -6269,12 +6278,15 @@ void ring_buffer_iter_advance(struct ring_buffer_iter *iter)
{
struct ring_buffer_per_cpu *cpu_buffer = iter->cpu_buffer;
unsigned long flags;
+ bool dolock;
- raw_spin_lock_irqsave(&cpu_buffer->reader_lock, flags);
+ local_irq_save(flags);
+ dolock = rb_reader_lock(cpu_buffer);
iter->missed_events = 0;
rb_advance_iter(iter);
- raw_spin_unlock_irqrestore(&cpu_buffer->reader_lock, flags);
+ rb_reader_unlock(cpu_buffer, dolock);
+ local_irq_restore(flags);
}
EXPORT_SYMBOL_GPL(ring_buffer_iter_advance);
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 09/18] ring-buffer: Use irqsave for the reader lock in ring_buffer_poll_remote
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort, Sashiko
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
Calling rb_wakeups with the reader lock but interrupts enabled can lead
to a deadlock: the irq_work might run on the same CPU, but will
block when acquiring that very same reader spinlock.
First, rb_wakeups doesn't even need to be called under the reader lock.
Move the function outside of the reader lock scope.
Second, the reader lock must be called with IRQs disabled anyway. Use
the irqsave variant of the spinlock.
Fixes: 2e67fabd8b77 ("ring-buffer: Introduce ring-buffer remotes")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c
index 7b07d2004cc6..183326633037 100644
--- a/kernel/trace/ring_buffer.c
+++ b/kernel/trace/ring_buffer.c
@@ -6628,13 +6628,17 @@ int ring_buffer_poll_remote(struct trace_buffer *buffer, int cpu)
struct ring_buffer_per_cpu *cpu_buffer;
if (cpu != RING_BUFFER_ALL_CPUS) {
+ bool wakeup;
+
if (!cpumask_test_cpu(cpu, buffer->cpumask))
return -EINVAL;
cpu_buffer = buffer->buffers[cpu];
- guard(raw_spinlock)(&cpu_buffer->reader_lock);
- if (rb_read_remote_meta_page(cpu_buffer))
+ scoped_guard(raw_spinlock_irqsave, &cpu_buffer->reader_lock)
+ wakeup = rb_read_remote_meta_page(cpu_buffer);
+
+ if (wakeup)
rb_wakeups(buffer, cpu_buffer);
return 0;
@@ -6649,7 +6653,7 @@ int ring_buffer_poll_remote(struct trace_buffer *buffer, int cpu)
for_each_buffer_cpu(buffer, cpu) {
cpu_buffer = buffer->buffers[cpu];
- guard(raw_spinlock)(&cpu_buffer->reader_lock);
+ guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock);
rb_read_remote_meta_page(cpu_buffer);
}
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 08/18] tracing/remotes: selftests: Prefix hypervisor folder
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
Avoid interleaving run tests by prefixing the hypervisor folder.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/hypervisor/buffer_size.tc b/tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/buffer_size.tc
similarity index 100%
rename from tools/testing/selftests/ftrace/test.d/remotes/hypervisor/buffer_size.tc
rename to tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/buffer_size.tc
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/hypervisor/dmesg.tc b/tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/dmesg.tc
similarity index 100%
rename from tools/testing/selftests/ftrace/test.d/remotes/hypervisor/dmesg.tc
rename to tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/dmesg.tc
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/hypervisor/hotplug.tc b/tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/hotplug.tc
similarity index 100%
rename from tools/testing/selftests/ftrace/test.d/remotes/hypervisor/hotplug.tc
rename to tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/hotplug.tc
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/hypervisor/reset.tc b/tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/reset.tc
similarity index 100%
rename from tools/testing/selftests/ftrace/test.d/remotes/hypervisor/reset.tc
rename to tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/reset.tc
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/hypervisor/trace.tc b/tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/trace.tc
similarity index 100%
rename from tools/testing/selftests/ftrace/test.d/remotes/hypervisor/trace.tc
rename to tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/trace.tc
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/hypervisor/trace_pipe.tc b/tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/trace_pipe.tc
similarity index 100%
rename from tools/testing/selftests/ftrace/test.d/remotes/hypervisor/trace_pipe.tc
rename to tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/trace_pipe.tc
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/hypervisor/unloading.tc b/tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/unloading.tc
similarity index 100%
rename from tools/testing/selftests/ftrace/test.d/remotes/hypervisor/unloading.tc
rename to tools/testing/selftests/ftrace/test.d/remotes/00hypervisor/unloading.tc
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply
* [PATCH v2 07/18] tracing/remotes: selftests: Add a test for the dmesg tracefs file
From: Vincent Donnefort @ 2026-06-05 16:38 UTC (permalink / raw)
To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
Cc: kernel-team, linux-kernel, Vincent Donnefort
In-Reply-To: <20260605163825.1762953-1-vdonnefort@google.com>
Exercise the newly introduced dmesg tracefs file that turns on and off
the dmesg redirection.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/dmesg.tc b/tools/testing/selftests/ftrace/test.d/remotes/dmesg.tc
new file mode 100644
index 000000000000..aebeea9dbab6
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/remotes/dmesg.tc
@@ -0,0 +1,72 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: Test trace remote dmesg redirection
+# requires: remotes/test
+
+. $TEST_DIR/remotes/functions
+
+test_dmesg()
+{
+ echo 0 > tracing_on
+ assert_unloaded
+
+ #
+ # Test dmesg on/off when tracing is disabled
+ #
+ echo 1 > dmesg
+ test $(cat dmesg) -eq 1
+ assert_loaded
+
+ echo 0 > dmesg
+ test $(cat dmesg) -eq 0
+ assert_unloaded
+
+ #
+ # Test events are logged to dmesg
+ #
+ dmesg -c > /dev/null
+
+ echo 1 > tracing_on
+ assert_loaded
+ echo 1 > dmesg
+ test $(cat dmesg) -eq 1
+
+ nr_events=128
+ for i in $(seq 1 $nr_events); do
+ echo $i > write_event
+ done
+
+ sleep 1
+ output=$(mktemp $TMPDIR/remote_test.XXXXXX)
+ dmesg | grep "selftest id=" | sed 's/^[^]]*] //'> $output
+
+ check_trace 1 $nr_events $output
+
+ rm $output
+
+ #
+ # Disable dmesg and Test events were not consumed by dmesg
+ #
+ echo 0 > dmesg
+ test $(cat dmesg) -eq 0
+
+ start_id=$(($nr_events + 1))
+ end_id=$(($start_id + $nr_events))
+
+ for i in $(seq $start_id $end_id); do
+ echo $i > write_event
+ done
+
+ sleep 1
+
+ output=$(dump_trace_pipe)
+ check_trace $start_id $end_id $output
+ rm $output
+}
+
+if [ -z "$SOURCE_REMOTE_TEST" ]; then
+ set -e
+
+ setup_remote_test
+ test_dmesg
+fi
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/functions b/tools/testing/selftests/ftrace/test.d/remotes/functions
index 05224fac3653..4a14aa72fdf0 100644
--- a/tools/testing/selftests/ftrace/test.d/remotes/functions
+++ b/tools/testing/selftests/ftrace/test.d/remotes/functions
@@ -8,6 +8,7 @@ setup_remote()
cd remotes/$name/
echo 0 > tracing_on
+ echo 0 > dmesg
clear_trace
echo 7 > buffer_size_kb
echo 0 > events/enable
diff --git a/tools/testing/selftests/ftrace/test.d/remotes/hypervisor/dmesg.tc b/tools/testing/selftests/ftrace/test.d/remotes/hypervisor/dmesg.tc
new file mode 100644
index 000000000000..bf4a3c145e7a
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/remotes/hypervisor/dmesg.tc
@@ -0,0 +1,11 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: Test the hypervisor trace dmesg redirection
+# requires: remotes/hypervisor/write_event
+
+SOURCE_REMOTE_TEST=1
+. $TEST_DIR/remotes/dmesg.tc
+
+set -e
+setup_remote "hypervisor"
+test_dmesg
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox