* 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
* [PATCH v2 06/18] tracing/remotes: Add 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>
When enabled, the dmesg tracefs file enables the redirection of all
events to dmesg. This is similar to tp_printk.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index 2271d54eb3dd..19dfa355b7f3 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -21,6 +21,7 @@
enum tri_type {
TRI_CONSUMING,
TRI_NONCONSUMING,
+ TRI_DMESG,
};
struct trace_remote_iterator {
@@ -43,6 +44,7 @@ struct trace_remote {
void *priv;
struct trace_buffer *trace_buffer;
struct trace_buffer_desc *trace_buffer_desc;
+ struct trace_remote_iterator *dmesg;
struct dentry *dentry;
struct eventfs_inode *eventfs_root;
struct eventfs_inode *eventfs_subdir;
@@ -394,10 +396,15 @@ static int __alloc_ring_buffer_iter(struct trace_remote_iterator *iter, int cpu)
return 0;
}
+static bool trace_remote_do_dmesg(struct trace_remote *remote);
+
static void trace_remote_do_poll(struct trace_remote *remote)
{
+ bool yield;
+
ring_buffer_poll_remote(remote->trace_buffer, RING_BUFFER_ALL_CPUS);
- schedule_delayed_work(&remote->poll_work, msecs_to_jiffies(remote->poll_ms));
+ yield = trace_remote_do_dmesg(remote);
+ schedule_delayed_work(&remote->poll_work, yield ? 0 : msecs_to_jiffies(remote->poll_ms));
}
static void __poll_remote(struct work_struct *work)
@@ -452,6 +459,14 @@ static struct trace_remote_iterator
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;
@@ -486,6 +501,11 @@ static void trace_remote_iter_free(struct trace_remote_iterator *iter)
lockdep_assert_held(&remote->lock);
switch (iter->type) {
+ case TRI_DMESG:
+ WARN_ON_ONCE(remote->dmesg != iter);
+ smp_store_release(&remote->dmesg, NULL);
+ flush_delayed_work(&remote->poll_work);
+ fallthrough;
case TRI_CONSUMING:
trace_remote_dec_poll(remote);
break;
@@ -498,13 +518,24 @@ static void trace_remote_iter_free(struct trace_remote_iterator *iter)
trace_remote_put(remote);
}
+static bool trace_remote_iter_is_consuming(struct trace_remote_iterator *iter)
+{
+ switch (iter->type) {
+ case TRI_CONSUMING:
+ case TRI_DMESG:
+ return true;
+ default:
+ return false;
+ }
+}
+
static void trace_remote_iter_read_start(struct trace_remote_iterator *iter)
{
struct trace_remote *remote = iter->remote;
int cpu = iter->cpu;
/* Acquire global reader lock */
- if (cpu == RING_BUFFER_ALL_CPUS && iter->type == TRI_CONSUMING)
+ if (cpu == RING_BUFFER_ALL_CPUS && trace_remote_iter_is_consuming(iter))
down_write(&remote->reader_lock);
else
down_read(&remote->reader_lock);
@@ -521,7 +552,7 @@ static void trace_remote_iter_read_start(struct trace_remote_iterator *iter)
if (WARN_ON_ONCE(!remote->pcpu_reader_locks))
return;
- if (iter->type == TRI_CONSUMING)
+ if (trace_remote_iter_is_consuming(iter))
down_write(&remote->pcpu_reader_locks[cpu]);
else
down_read(&remote->pcpu_reader_locks[cpu]);
@@ -538,14 +569,14 @@ static void trace_remote_iter_read_finished(struct trace_remote_iterator *iter)
* No need for the remote lock here, iter holds a reference on
* remote->nr_readers
*/
- if (iter->type == TRI_CONSUMING)
+ if (trace_remote_iter_is_consuming(iter))
up_write(&remote->pcpu_reader_locks[cpu]);
else
up_read(&remote->pcpu_reader_locks[cpu]);
}
/* Release global reader lock */
- if (cpu == RING_BUFFER_ALL_CPUS && iter->type == TRI_CONSUMING)
+ if (cpu == RING_BUFFER_ALL_CPUS && trace_remote_iter_is_consuming(iter))
up_write(&remote->reader_lock);
else
up_read(&remote->reader_lock);
@@ -562,10 +593,9 @@ __peek_event(struct trace_remote_iterator *iter, int cpu, u64 *ts, unsigned long
struct ring_buffer_event *rb_evt;
struct ring_buffer_iter *rb_iter;
- switch (iter->type) {
- case TRI_CONSUMING:
+ if (trace_remote_iter_is_consuming(iter)) {
return ring_buffer_peek(iter->remote->trace_buffer, cpu, ts, lost_events);
- case TRI_NONCONSUMING:
+ } else {
rb_iter = __get_rb_iter(iter, cpu);
if (!rb_iter)
return NULL;
@@ -629,14 +659,10 @@ static void trace_remote_iter_move(struct trace_remote_iterator *iter)
{
struct trace_buffer *trace_buffer = iter->remote->trace_buffer;
- switch (iter->type) {
- case TRI_CONSUMING:
+ if (trace_remote_iter_is_consuming(iter))
ring_buffer_consume(trace_buffer, iter->evt_cpu, NULL, NULL);
- break;
- case TRI_NONCONSUMING:
+ else
ring_buffer_iter_advance(__get_rb_iter(iter, iter->evt_cpu));
- break;
- }
}
static struct remote_event *trace_remote_find_event(struct trace_remote *remote, unsigned short id);
@@ -882,6 +908,86 @@ static const struct file_operations trace_fops = {
.release = trace_release,
};
+static bool trace_remote_do_dmesg(struct trace_remote *remote)
+{
+ struct trace_remote_iterator *iter = smp_load_acquire(&remote->dmesg);
+ unsigned int max_events = 1000;
+
+ if (!iter)
+ return false;
+
+ trace_remote_iter_read_start(iter);
+
+ while (trace_remote_iter_read_event(iter)) {
+ trace_seq_init(&iter->seq);
+
+ trace_remote_iter_print_event(iter);
+ if (!pr_emerg("%s", iter->seq.buffer))
+ break;
+
+ trace_remote_iter_move(iter);
+
+ if (!(--max_events))
+ break;
+ }
+
+ trace_remote_iter_read_finished(iter);
+
+ return !max_events;
+}
+
+static int trace_remote_enable_dmesg(struct trace_remote *remote, bool enable)
+{
+ struct trace_remote_iterator *iter = remote->dmesg;
+
+ lockdep_assert_held(&remote->lock);
+
+ if (enable == !!iter)
+ return 0;
+
+ if (enable) {
+ iter = trace_remote_iter(remote, RING_BUFFER_ALL_CPUS, TRI_DMESG);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+ } else {
+ trace_remote_iter_free(remote->dmesg);
+ /* trace_remote_iter_free has reset remote->dmesg */
+ }
+
+ return 0;
+}
+
+static ssize_t
+dmesg_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 val;
+ int ret;
+
+ ret = kstrtobool_from_user(ubuf, cnt, &val);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&remote->lock);
+
+ ret = trace_remote_enable_dmesg(remote, val);
+ if (ret)
+ return ret;
+
+ return cnt;
+}
+
+static int dmesg_show(struct seq_file *s, void *unused)
+{
+ struct trace_remote *remote = s->private;
+
+ seq_printf(s, "%d\n", !!remote->dmesg);
+
+ return 0;
+}
+DEFINE_TRACE_REMOTE_ATTRIBUTE(dmesg);
+
static int trace_remote_init_tracefs(const char *name, struct trace_remote *remote)
{
struct dentry *remote_d, *percpu_d, *d;
@@ -922,6 +1028,10 @@ static int trace_remote_init_tracefs(const char *name, struct trace_remote *remo
if (!d)
goto err;
+ d = trace_create_file("dmesg", TRACEFS_MODE_WRITE, remote_d, remote, &dmesg_fops);
+ if (!d)
+ goto err;
+
percpu_d = tracefs_create_dir("per_cpu", remote_d);
if (!percpu_d) {
pr_err("Failed to create tracefs dir "TRACEFS_DIR"%s/per_cpu/\n", name);
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 05/18] tracing/simple_ring_buffer: Add support for compressed length
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 array length is the total size in bytes of the data for the current
event. It is possible to compress this value into the event header type,
which has 28 unused types, saving 32 bits for sufficiently small events.
The compressed length is expressed as a multiple of the ring-buffer
alignment, 4-bytes by default. Enforces this alignment.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/simple_ring_buffer.c b/kernel/trace/simple_ring_buffer.c
index f4642f5adda3..1a97d17cca24 100644
--- a/kernel/trace/simple_ring_buffer.c
+++ b/kernel/trace/simple_ring_buffer.c
@@ -207,7 +207,15 @@ static unsigned long rb_event_size(unsigned long length)
{
struct ring_buffer_event *event;
- return length + RB_EVNT_HDR_SIZE + sizeof(event->array[0]);
+ if (!length)
+ length++;
+
+ length = ALIGN(length, RB_ALIGNMENT);
+
+ if (length > RB_MAX_SMALL_DATA)
+ length += sizeof(event->array[0]);
+
+ return length + RB_EVNT_HDR_SIZE;
}
static struct ring_buffer_event *
@@ -223,12 +231,15 @@ rb_event_add_ts_extend(struct ring_buffer_event *event, u64 delta)
static struct ring_buffer_event *
simple_rb_reserve_next(struct simple_rb_per_cpu *cpu_buffer, unsigned long length, u64 timestamp)
{
- unsigned long ts_ext_size = 0, event_size = rb_event_size(length);
struct simple_buffer_page *tail = cpu_buffer->tail_page;
+ unsigned long event_size, array_size, ts_ext_size = 0;
struct ring_buffer_event *event;
u32 write, prev_write;
u64 time_delta;
+ event_size = rb_event_size(length);
+ array_size = event_size - RB_EVNT_HDR_SIZE;
+
time_delta = timestamp - cpu_buffer->write_stamp;
if (test_time_stamp(time_delta))
@@ -259,9 +270,13 @@ simple_rb_reserve_next(struct simple_rb_per_cpu *cpu_buffer, unsigned long lengt
time_delta = 0;
}
- event->type_len = 0;
+ if (length > RB_MAX_SMALL_DATA) {
+ event->type_len = 0;
+ event->array[0] = array_size;
+ } else {
+ event->type_len = DIV_ROUND_UP(array_size, RB_ALIGNMENT);
+ }
event->time_delta = time_delta;
- event->array[0] = event_size - RB_EVNT_HDR_SIZE;
return event;
}
@@ -284,7 +299,7 @@ void *simple_ring_buffer_reserve(struct simple_rb_per_cpu *cpu_buffer, unsigned
rb_event = simple_rb_reserve_next(cpu_buffer, length, timestamp);
- return &rb_event->array[1];
+ return rb_event->type_len ? &rb_event->array[0] : &rb_event->array[1];
}
EXPORT_SYMBOL_GPL(simple_ring_buffer_reserve);
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 04/18] tracing/remotes: Use a single per-remote polling work
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>
Having a per-iterator polling work is wasteful when logging several
trace_remote per_cpu/trace_pipe files in parallel. This result in one
work running per-CPU, where only one would suffice.
Transition to a single per-remote polling work, scheduled on the first
consumer creation and stopped when the last consuming iterator is freed.
This blanket polls all CPUs, regardless of which ones are actually being
read. This is acceptable because the poll consists of reading the
meta-page, which is a fast operation. Also, it is more common to log all
CPUs in the system than only one, so this use-case should be favoured.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index 71f6cda0fbd4..2271d54eb3dd 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -26,7 +26,6 @@ enum tri_type {
struct trace_remote_iterator {
struct trace_remote *remote;
struct trace_seq seq;
- struct delayed_work poll_work;
unsigned long lost_events;
u64 ts;
struct ring_buffer_iter *rb_iter;
@@ -56,6 +55,8 @@ struct trace_remote {
struct rw_semaphore *pcpu_reader_locks;
unsigned int nr_readers;
unsigned int poll_ms;
+ struct delayed_work poll_work;
+ unsigned int poll_cnt;
bool tracing_on;
};
@@ -350,17 +351,6 @@ static bool trace_remote_has_cpu(struct trace_remote *remote, int cpu)
return ring_buffer_poll_remote(remote->trace_buffer, cpu) == 0;
}
-static void __poll_remote(struct work_struct *work)
-{
- struct delayed_work *dwork = to_delayed_work(work);
- struct trace_remote_iterator *iter;
-
- iter = container_of(dwork, struct trace_remote_iterator, poll_work);
- ring_buffer_poll_remote(iter->remote->trace_buffer, iter->cpu);
- schedule_delayed_work((struct delayed_work *)work,
- msecs_to_jiffies(iter->remote->poll_ms));
-}
-
static void __free_ring_buffer_iter(struct trace_remote_iterator *iter, int cpu)
{
if (cpu != RING_BUFFER_ALL_CPUS) {
@@ -404,6 +394,36 @@ static int __alloc_ring_buffer_iter(struct trace_remote_iterator *iter, int cpu)
return 0;
}
+static void trace_remote_do_poll(struct trace_remote *remote)
+{
+ ring_buffer_poll_remote(remote->trace_buffer, RING_BUFFER_ALL_CPUS);
+ schedule_delayed_work(&remote->poll_work, msecs_to_jiffies(remote->poll_ms));
+}
+
+static void __poll_remote(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+
+ trace_remote_do_poll(container_of(dwork, struct trace_remote, poll_work));
+}
+
+static void trace_remote_inc_poll(struct trace_remote *remote)
+{
+ /* poll_cnt <= nr_readers, inherits its overflow protection */
+ if (!remote->poll_cnt++)
+ trace_remote_do_poll(remote);
+}
+
+static void trace_remote_dec_poll(struct trace_remote *remote)
+{
+ if (WARN_ON_ONCE(!remote->poll_cnt))
+ return;
+
+ remote->poll_cnt--;
+ if (!remote->poll_cnt)
+ cancel_delayed_work_sync(&remote->poll_work);
+}
+
static struct trace_remote_iterator
*trace_remote_iter(struct trace_remote *remote, int cpu, enum tri_type type)
{
@@ -433,9 +453,7 @@ static struct trace_remote_iterator
switch (type) {
case TRI_CONSUMING:
- ring_buffer_poll_remote(remote->trace_buffer, cpu);
- INIT_DELAYED_WORK(&iter->poll_work, __poll_remote);
- schedule_delayed_work(&iter->poll_work, msecs_to_jiffies(remote->poll_ms));
+ trace_remote_inc_poll(remote);
break;
case TRI_NONCONSUMING:
ret = __alloc_ring_buffer_iter(iter, cpu);
@@ -469,7 +487,7 @@ static void trace_remote_iter_free(struct trace_remote_iterator *iter)
switch (iter->type) {
case TRI_CONSUMING:
- cancel_delayed_work_sync(&iter->poll_work);
+ trace_remote_dec_poll(remote);
break;
case TRI_NONCONSUMING:
__free_ring_buffer_iter(iter, iter->cpu);
@@ -1002,6 +1020,7 @@ int trace_remote_register(const char *name, struct trace_remote_callbacks *cbs,
remote->poll_ms = 100;
mutex_init(&remote->lock);
init_rwsem(&remote->reader_lock);
+ INIT_DELAYED_WORK(&remote->poll_work, __poll_remote);
guard(mutex)(&trace_remotes_lock);
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 03/18] tracing/remotes: Use kstrtobool for boolean tracefs files
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>
Use kstrtobool in trace_remote.c where possible. This is more user-friendly
as it allows a better variety of input strings.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index 9b27c7bd6040..71f6cda0fbd4 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -235,10 +235,10 @@ tracing_on_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t
{
struct seq_file *seq = filp->private_data;
struct trace_remote *remote = seq->private;
- unsigned long val;
+ bool val;
int ret;
- ret = kstrtoul_from_user(ubuf, cnt, 10, &val);
+ ret = kstrtobool_from_user(ubuf, cnt, &val);
if (ret)
return ret;
@@ -1154,10 +1154,10 @@ static ssize_t remote_event_enable_write(struct file *filp, const char __user *u
struct seq_file *seq = filp->private_data;
struct remote_event *evt = seq->private;
struct trace_remote *remote = evt->remote;
- u8 enable;
+ bool enable;
int ret;
- ret = kstrtou8_from_user(ubuf, count, 10, &enable);
+ ret = kstrtobool_from_user(ubuf, count, &enable);
if (ret)
return ret;
@@ -1238,10 +1238,10 @@ static ssize_t remote_events_dir_enable_write(struct file *filp, const char __us
size_t count, loff_t *ppos)
{
struct trace_remote *remote = file_inode(filp)->i_private;
+ bool enable;
int i, ret;
- u8 enable;
- ret = kstrtou8_from_user(ubuf, count, 10, &enable);
+ ret = kstrtobool_from_user(ubuf, count, &enable);
if (ret)
return ret;
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v2 02/18] tracing/remotes: Release tracefs,eventfs on registration failure
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 trace_remote_register(), if registration of events or the init
callback fails, the created tracefs and eventfs directories are leaked.
Release the entire eventfs and tracefs hierarchy on trace_remote
registration failure.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index 9f1669d433d5..9b27c7bd6040 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -45,7 +45,8 @@ struct trace_remote {
struct trace_buffer *trace_buffer;
struct trace_buffer_desc *trace_buffer_desc;
struct dentry *dentry;
- struct eventfs_inode *eventfs;
+ struct eventfs_inode *eventfs_root;
+ struct eventfs_inode *eventfs_subdir;
struct remote_event *events;
unsigned long nr_events;
unsigned long trace_buffer_size;
@@ -60,6 +61,7 @@ struct trace_remote {
static DEFINE_MUTEX(trace_remotes_lock);
static LIST_HEAD(trace_remotes);
+static struct dentry *trace_remotes_root;
static bool trace_remote_loaded(struct trace_remote *remote)
{
@@ -865,23 +867,21 @@ static const struct file_operations trace_fops = {
static int trace_remote_init_tracefs(const char *name, struct trace_remote *remote)
{
struct dentry *remote_d, *percpu_d, *d;
- static struct dentry *root;
- static DEFINE_MUTEX(lock);
bool root_inited = false;
int cpu;
- guard(mutex)(&lock);
+ lockdep_assert_held(&trace_remotes_lock);
- if (!root) {
- root = tracefs_create_dir(TRACEFS_DIR, NULL);
- if (!root) {
+ if (!trace_remotes_root) {
+ trace_remotes_root = tracefs_create_dir(TRACEFS_DIR, NULL);
+ if (!trace_remotes_root) {
pr_err("Failed to create tracefs dir "TRACEFS_DIR"\n");
return -ENOMEM;
}
root_inited = true;
}
- remote_d = tracefs_create_dir(name, root);
+ remote_d = tracefs_create_dir(name, trace_remotes_root);
if (!remote_d) {
pr_err("Failed to create tracefs dir "TRACEFS_DIR"%s/\n", name);
goto err;
@@ -939,8 +939,8 @@ static int trace_remote_init_tracefs(const char *name, struct trace_remote *remo
err:
if (root_inited) {
- tracefs_remove(root);
- root = NULL;
+ tracefs_remove(trace_remotes_root);
+ trace_remotes_root = NULL;
} else {
tracefs_remove(remote_d);
}
@@ -948,8 +948,26 @@ static int trace_remote_init_tracefs(const char *name, struct trace_remote *remo
return -ENOMEM;
}
+static void trace_remote_remove_tracefs(struct trace_remote *remote)
+{
+ lockdep_assert_held(&trace_remotes_lock);
+
+ if (!remote->dentry)
+ return;
+
+ tracefs_remove(remote->dentry);
+ remote->dentry = NULL;
+
+ if (!list_empty(&trace_remotes))
+ return;
+
+ tracefs_remove(trace_remotes_root);
+ trace_remotes_root = NULL;
+}
+
static int trace_remote_register_events(const char *remote_name, struct trace_remote *remote,
struct remote_event *events, size_t nr_events);
+static void trace_remote_unregister_events(struct trace_remote *remote);
/**
* trace_remote_register() - Register a Tracefs remote
@@ -972,10 +990,9 @@ static int trace_remote_register_events(const char *remote_name, struct trace_re
int trace_remote_register(const char *name, struct trace_remote_callbacks *cbs, void *priv,
struct remote_event *events, size_t nr_events)
{
- struct trace_remote *remote;
+ struct trace_remote *remote __free(kfree) = kzalloc_obj(*remote);
int ret;
- remote = kzalloc_obj(*remote);
if (!remote)
return -ENOMEM;
@@ -986,13 +1003,15 @@ int trace_remote_register(const char *name, struct trace_remote_callbacks *cbs,
mutex_init(&remote->lock);
init_rwsem(&remote->reader_lock);
- if (trace_remote_init_tracefs(name, remote)) {
- kfree(remote);
- return -ENOMEM;
- }
+ guard(mutex)(&trace_remotes_lock);
+
+ ret = trace_remote_init_tracefs(name, remote);
+ if (ret)
+ return ret;
ret = trace_remote_register_events(name, remote, events, nr_events);
if (ret) {
+ trace_remote_remove_tracefs(remote);
pr_err("Failed to register events for trace remote '%s' (%d)\n",
name, ret);
return ret;
@@ -1000,13 +1019,16 @@ int trace_remote_register(const char *name, struct trace_remote_callbacks *cbs,
ret = cbs->init ? cbs->init(remote->dentry, priv) : 0;
if (ret) {
+ trace_remote_unregister_events(remote);
+ trace_remote_remove_tracefs(remote);
pr_err("Init failed for trace remote '%s' (%d)\n", name, ret);
- } else {
- guard(mutex)(&trace_remotes_lock);
- list_add(&remote->node, &trace_remotes);
+ return ret;
}
- return ret;
+ list_add(&remote->node, &trace_remotes);
+ retain_and_null_ptr(remote);
+
+ return 0;
}
EXPORT_SYMBOL_GPL(trace_remote_register);
@@ -1341,7 +1363,6 @@ static int remote_events_dir_callback(const char *name, umode_t *mode, void **da
static int trace_remote_init_eventfs(const char *remote_name, struct trace_remote *remote,
struct remote_event *evt)
{
- struct eventfs_inode *eventfs = remote->eventfs;
static struct eventfs_entry dir_entries[] = {
{
.name = "enable",
@@ -1366,35 +1387,37 @@ static int trace_remote_init_eventfs(const char *remote_name, struct trace_remot
.callback = remote_event_callback,
}
};
- bool eventfs_create = false;
+ struct eventfs_inode *eventfs_root, *eventfs_subdir, *e;
- if (!eventfs) {
- eventfs = eventfs_create_events_dir("events", remote->dentry, dir_entries,
- ARRAY_SIZE(dir_entries), remote);
- if (IS_ERR(eventfs))
- return PTR_ERR(eventfs);
+ eventfs_root = remote->eventfs_root;
+ eventfs_subdir = remote->eventfs_subdir;
+ if (!eventfs_root) {
+ eventfs_root = eventfs_create_events_dir("events", remote->dentry, dir_entries,
+ ARRAY_SIZE(dir_entries), remote);
+ if (IS_ERR(eventfs_root))
+ return PTR_ERR(eventfs_root);
/*
* Create similar hierarchy as local events even if a single system is supported at
* the moment
*/
- eventfs = eventfs_create_dir(remote_name, eventfs, NULL, 0, NULL);
- if (IS_ERR(eventfs))
- return PTR_ERR(eventfs);
-
- remote->eventfs = eventfs;
- eventfs_create = true;
+ eventfs_subdir = eventfs_create_dir(remote_name, eventfs_root, NULL, 0, NULL);
+ if (IS_ERR(eventfs_subdir)) {
+ eventfs_remove_events_dir(eventfs_root);
+ return PTR_ERR(eventfs_subdir);
+ }
}
- eventfs = eventfs_create_dir(evt->name, eventfs, entries, ARRAY_SIZE(entries), evt);
- if (IS_ERR(eventfs)) {
- if (eventfs_create) {
- eventfs_remove_events_dir(remote->eventfs);
- remote->eventfs = NULL;
- }
- return PTR_ERR(eventfs);
+ e = eventfs_create_dir(evt->name, eventfs_subdir, entries, ARRAY_SIZE(entries), evt);
+ if (IS_ERR(e)) {
+ if (!remote->eventfs_root)
+ eventfs_remove_events_dir(eventfs_root);
+ return PTR_ERR(e);
}
+ remote->eventfs_root = eventfs_root;
+ remote->eventfs_subdir = eventfs_subdir;
+
return 0;
}
@@ -1409,11 +1432,11 @@ static int trace_remote_attach_events(struct trace_remote *remote, struct remote
if (evt->remote)
return -EEXIST;
- evt->remote = remote;
-
/* We need events to be sorted for efficient lookup */
if (i && evt->id <= events[i - 1].id)
return -EINVAL;
+
+ evt->remote = remote;
}
remote->events = events;
@@ -1422,14 +1445,33 @@ static int trace_remote_attach_events(struct trace_remote *remote, struct remote
return 0;
}
+static void trace_remote_detach_events(struct trace_remote *remote, struct remote_event *events,
+ size_t nr_events)
+{
+ int i;
+
+ for (i = 0; i < nr_events; i++) {
+ struct remote_event *evt = &events[i];
+
+ if (evt->remote == remote)
+ evt->remote = NULL;
+ }
+
+ remote->events = NULL;
+ remote->nr_events = 0;
+}
+
static int trace_remote_register_events(const char *remote_name, struct trace_remote *remote,
struct remote_event *events, size_t nr_events)
{
int i, ret;
ret = trace_remote_attach_events(remote, events, nr_events);
- if (ret)
+ if (ret) {
+ /* It is safe to call detach on a half-registered array */
+ trace_remote_detach_events(remote, events, nr_events);
return ret;
+ }
for (i = 0; i < nr_events; i++) {
struct remote_event *evt = &events[i];
@@ -1443,6 +1485,13 @@ static int trace_remote_register_events(const char *remote_name, struct trace_re
return 0;
}
+static void trace_remote_unregister_events(struct trace_remote *remote)
+{
+ trace_remote_detach_events(remote, remote->events, remote->nr_events);
+ if (remote->eventfs_root)
+ eventfs_remove_events_dir(remote->eventfs_root);
+}
+
static int __cmp_events(const void *key, const void *data)
{
const struct remote_event *evt = data;
--
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