Linux Trace Kernel
 help / color / mirror / Atom feed
* Re: [RFC 2/3] arm64: kprobes: Allow reentering kprobes while single-stepping
From: Masami Hiramatsu @ 2026-07-08 14:12 UTC (permalink / raw)
  To: Hongyan Xia
  Cc: Pu Hu, ada.coupriediaz@arm.com, catalin.marinas@arm.com,
	davem@davemloft.net, Jiazi Li,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	naveen@kernel.org, will@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <68189403-9728-4938-8ee2-924e4702eab7@transsion.com>

On Wed, 8 Jul 2026 07:12:39 +0000
Hongyan Xia <hongyan.xia@transsion.com> wrote:

> On 7/8/2026 8:54 AM, Masami Hiramatsu wrote:
> > On Mon, 6 Jul 2026 08:36:49 +0000
> > Pu Hu <hupu@transsion.com> wrote:
> >
> >> From: Pu Hu <hupu@transsion.com>
> >>
> >> A kprobe can be hit while another kprobe is in KPROBE_HIT_SS state. This
> >> can happen when tracing or perf code runs from the debug exception path
> >> while the first kprobe is preparing or executing its out-of-line
> >> single-step instruction.
> >>
> >> Currently arm64 treats a kprobe hit in KPROBE_HIT_SS as unrecoverable,
> >> the same as a hit in KPROBE_REENTER. This is too strict. A hit in
> >> KPROBE_HIT_SS is still a one-level reentry and can be handled by saving
> >> the current kprobe state and setting up single-step for the new probe,
> >> just like reentry from KPROBE_HIT_ACTIVE or KPROBE_HIT_SSDONE.
> >>
> >> The truly unrecoverable case is hitting another kprobe while already in
> >> KPROBE_REENTER, because the reentry save area has already been consumed.
> >>
> >> Move KPROBE_HIT_SS to the recoverable reentry cases and leave
> >> KPROBE_REENTER as the unrecoverable nested reentry case.
> >>
> >> This mirrors the x86 fix in commit 6a5022a56ac3
> >> ("kprobes/x86: Allow to handle reentered kprobe on single-stepping").
> >
> > Can you also check the Sashiko comment?
> >
> > https://sashiko.dev/#/patchset/20260706083636.159883-1-hupu%40transsion.com?part=2
> >
> > This seems indicating potentially brakage of reenter kprobes on arm64.
> > But is it possible to hit another kprobe while SS on arm64? It is
> > the same question about the previous one, can NMI happens during
> > the single stepping? (maybe yes, because it is non-maskable)
> 
> It is possible as this is what we hit. There are
> preempt_enable/disable() calls during SS which can trigger perf events
> sampling the user stack, which may then trigger page faults and send the
> CPU into one more level of exception context. I believe this is what you
> saw in 6a5022a56ac3?

Ah, it was more generic perf NMI case, not only preempt_enable/disable().
But anyway, the result is same.

> > Anyway, for making it safer, we need to add saved_irqflags to prev_kprobe.
> 
> Yes, this seems to be a legit bug that needs to be fixed. Thank you.

OK, thanks!


-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>

^ permalink raw reply

* [PATCH v1] tracing/remotes: Fix leak in trace_remote_alloc_buffer() error path
From: Vincent Donnefort @ 2026-07-08 13:32 UTC (permalink / raw)
  To: rostedt, mhiramat, mathieu.desnoyers, linux-trace-kernel
  Cc: kernel-team, linux-kernel, Vincent Donnefort, Sashiko

If page allocation fails in trace_remote_alloc_buffer(), desc->nr_cpus
is not yet incremented for the current CPU. As a consequence, on error,
half-allocated rb_desc will not be freed in trace_remote_free_buffer().

Include the failing CPU in desc->nr_cpus before going to the error path.

Fixes: 96e43537af54 ("tracing: Introduce trace remotes")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>

diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index 2a6cc000ec98..62d3d431c309 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -1008,8 +1008,10 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size,
 
 		for (id = 0; id < nr_pages; id++) {
 			rb_desc->page_va[id] = (unsigned long)__get_free_page(GFP_KERNEL);
-			if (!rb_desc->page_va[id])
+			if (!rb_desc->page_va[id]) {
+				desc->nr_cpus++; /* Free this partially-allocated rb_desc */
 				goto err;
+			}
 
 			rb_desc->nr_page_va++;
 		}

base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
-- 
2.55.0.795.g602f6c329a-goog


^ permalink raw reply related

* Re: [BUG] tracing: Too many tries to read user space
From: Steven Rostedt @ 2026-07-08 13:18 UTC (permalink / raw)
  To: Jeongho Choi
  Cc: linux-trace-kernel, linux-kernel, mhiramat, ji2yoon.jo,
	minki.jang, hajun.sung
In-Reply-To: <20260708123753.GB1386@KORCO121415.samsungds.net>

On Wed, 8 Jul 2026 21:37:53 +0900
Jeongho Choi <jh1012.choi@samsung.com> wrote:

> The code at the WARN location mentioned in the log above is as follows.
> 
> 7374                 if (WARN_ONCE(trys++ > 100, "Error: Too many
>   tries to read user space"))
> 7375                         return NULL;
> 

This happens when something forces a schedule.

> 
> Our current analysis is as follows:
> 
> In the Gmail process, during a low memory situation, LMKD writes strings
> to /sys/kernel/tracing/trace_marker for systrace recording. At the same
> time, it broadcasts a sigkill due to low memory, which is causing the
> LMKD trace marker operation to stall.
> 

Can you see what is being scheduled in? Perhaps use the persistent ring
buffer (if you can) and enable sched_switch tracepoint in it.

The loop is this:

	do {
		/*
		 * It is possible that something is trying to migrate this
		 * task. What happens then, is when preemption is enabled,
		 * the migration thread will preempt this task, try to
		 * migrate it, fail, then let it run again. That will
		 * cause this to loop again and never succeed.
		 * On failures, enabled and disable preemption with
		 * migration enabled, to allow the migration thread to
		 * migrate this task.
		 */
		if (trys) {
			preempt_enable_notrace();
			preempt_disable_notrace();
			cpu = smp_processor_id();
			buffer = per_cpu_ptr(tinfo->tbuf, cpu)->buf;
		}

		/*
		 * If for some reason, copy_from_user() always causes a context
		 * switch, this would then cause an infinite loop.
		 * If this task is preempted by another user space task, it
		 * will cause this task to try again. But just in case something
		 * changes where the copying from user space causes another task
		 * to run, prevent this from going into an infinite loop.
		 * 100 tries should be plenty.
		 */
		if (WARN_ONCE(trys++ > 100, "Error: Too many tries to read user space"))
			return NULL;

		/* Read the current CPU context switch counter */
		cnt = nr_context_switches_cpu(cpu);

		/*
		 * Preemption is going to be enabled, but this task must
		 * remain on this CPU.
		 */
		migrate_disable();

		/*
		 * Now preemption is being enabled and another task can come in
		 * and use the same buffer and corrupt our data.
		 */
		preempt_enable_notrace();

		/* Make sure preemption is enabled here */
		lockdep_assert_preemption_enabled();

		if (copy_func) {
			ret = copy_func(buffer, ptr, size, data);
		} else {
			ret = __copy_from_user(buffer, ptr, size);
		}

		preempt_disable_notrace();
		migrate_enable();

		/* if it faulted, no need to test if the buffer was corrupted */
		if (ret)
			return NULL;

		/*
		 * Preemption is disabled again, now check the per CPU context
		 * switch counter. If it doesn't match, then another user space
		 * process may have schedule in and corrupted our buffer. In that
		 * case the copying must be retried.
		 */
	} while (nr_context_switches_cpu(cpu) != cnt);

What it does is to write into a per CPU buffer from user space. If it
schedules out while trying this, it will try again as another task could
have come in and corrupted the buffer.

For some reason, when preemption is enabled to copy from user, something
is forcing a schedule of the process. If we can figure out what is doing
that, perhaps we can fix this. A similar thing happened with the migration
thread that commit edca33a56297d ("tracing: Fix failure to read user space
from system call trace events") fixed.

-- Steve

^ permalink raw reply

* Re: [PATCH v3 04/11] arm64/mm: Add set_memory_device() and set_memory_normal()
From: Thierry Reding @ 2026-07-08 12:50 UTC (permalink / raw)
  To: Will Deacon
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Hunter,
	David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Sowjanya Komatineni, Luca Ceresoli,
	Mikko Perttunen, Yury Norov, Rasmus Villemoes, Russell King,
	Alexander Gordeev, Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
	Christian Borntraeger, Sven Schnelle, Andrew Morton,
	David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
	Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
	Marek Szyprowski, Robin Murphy, Sumit Semwal, Benjamin Gaignard,
	Brian Starkey, John Stultz, T.J. Mercier, Christian König,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Catalin Marinas, Thierry Reding, devicetree, linux-tegra,
	linux-kernel, dri-devel, linux-media, linux-arm-kernel,
	linux-s390, linux-mm, iommu, linaro-mm-sig, linux-trace-kernel,
	Thierry Reding, Chun Ng
In-Reply-To: <akzikTrmhMsvkNVY@willie-the-truck>

[-- Attachment #1: Type: text/plain, Size: 6397 bytes --]

On Tue, Jul 07, 2026 at 12:27:13PM +0100, Will Deacon wrote:
> On Mon, Jul 06, 2026 at 03:49:24PM +0200, Thierry Reding wrote:
> > On Fri, Jul 03, 2026 at 06:13:31PM +0100, Will Deacon wrote:
> > > On Thu, Jul 02, 2026 at 06:41:23PM +0200, Thierry Reding wrote:
> > > > On Thu, Jul 02, 2026 at 03:46:44PM +0200, Thierry Reding wrote:
> > > > > On Thu, Jul 02, 2026 at 10:18:47AM +0100, Will Deacon wrote:
> > > > > > On Wed, Jul 01, 2026 at 06:08:15PM +0200, Thierry Reding wrote:
> > > > > > > From: Chun Ng <chunn@nvidia.com>
> > > > > > > 
> > > > > > > Add helpers to swap PROT_NORMAL and PROT_DEVICE_nGnRnE protection bits
> > > > > > > on a kernel-linear-map range.
> > > > > > 
> > > > > > That sounds like a really terrible idea. Why is this necessary and how
> > > > > > does it interact with things like load_unaligned_zeropad()?
> > > > > 
> > > > > This is necessary because once the memory controller has walled off the
> > > > > new memory region the CPU must not access it under any circumstances or
> > > > > it'll cause the CPU to lock up (I think technically it'll hit an SError
> > > > > but in practice that just means it'll freeze, as far as I can tell).
> > > > > 
> > > > > Probably doesn't interact well at all with load_unaligned_zeropad().
> > > > > 
> > > > > > I think you should unmap the memory from the linear map and memremap()
> > > > > > it instead.
> > > > > 
> > > > > Given that the memory can never be accessed by the CPU after the memory
> > > > > controller locks it down, I don't think we'll even need memremap(). The
> > > > > only thing we really need is the sg_table we hand out via the DMA BUFs
> > > > > so that they can be used by device drivers to program their DMA engines
> > > > > internally.
> > > > > 
> > > > > Looking through some of the architecture code around this, shouldn't we
> > > > > simply be using set_memory_encrypted() and set_memory_decrypted() for
> > > > > this? While they might've been created for slightly other use-cases,
> > > > > they seem to be doing exactly what we want (i.e. remove the page range
> > > > > from the linear mapping and flushing it, or restoring the valid bit and
> > > > > standard permissions, respectively).
> > > > 
> > > > Ah... I guess we can't do it because we're not in a realm world and so
> > > > the early checks in __set_memory_enc_dec() would return early and turn
> > > > it into a no-op.
> > > > 
> > > > How about if I extract a common helper and provide set_memory_p() and
> > > > set_memory_np() in terms of those. Those are available on x86 and
> > > > PowerPC as well, so fairly standard. I suppose at that point we're
> > > > closer to set_memory_valid().
> > > 
> > > Why not just call set_direct_map_invalid_noflush() +
> > > flush_tlb_kernel_range() for each page? We already have APIs for this.
> > 
> > Having a "standard" helper with a fixed and documented purposed seemed
> > like a preferable approach for this particular case. We also may want to
> > make the driver that uses this buildable as a module, in which case we'd
> > need to export these rather low-level APIs. And then there's also the
> > fact that we typically call this on a rather large region of memory
> > (usually something like 512 MiB), so doing it page-by-page is rather
> > suboptimal.
> > 
> > > The big challenge I see with any linear map manipulation, however, is
> > > that it will rely on can_set_direct_map() which likely means you need to
> > > give up some performance and/or security to make this work. Does memory
> > > become inaccesible dynamically at runtime? If not, the best bet would
> > > be to describe it as a carveout in the DT and mark it as "no-map" so
> > > we avoid mapping it in the first place.
> > 
> > VPR exists in two modes: static and resizable. For static VPR we do
> > exactly that: describe it as carveout in DT with no-map and deal with it
> > accordingly in the driver. Resizable VPR is for device that have small
> > amounts of RAM. Content-protected video playback will in the worst case
> > consume around 1.8 GiB of RAM, so we want to be able to reuse for other
> > purposes when VPR is unused on those devices. In that case, the memory
> > is also described as a reserved-memory region in DT, but it is marked as
> > reusable so that it can be managed by CMA.
> > 
> > The resize operation is fairly slow to begin with because we need to
> > stall the GPU and put it into reset before the operation, then take it
> > out of reset and resume it afterwards.
> > 
> > What kind of performance impact do you expect?
> 
> You'll need to measure it, but we've seen reports of double-digit
> percentage regressions in performance and power. As I said, the problem
> is that you need to split the linear map to 4k page at runtime to unmap
> the dynamic carveout, but that isn't something that can be done on most
> CPUs. Therefore you end up having to use page-granular mappings for the
> entire thing, similarly to how 'rodata_full' drives can_set_direct_map()
> and the perf/power hit affects everything.
> 
> It's hard to know what to suggest... I wonder if any of the memory
> hotplug logic could help here?

I've read up on memory hotplug a bit and it sounds like it could fit
this really nicely. Given that we only use CMA (along with the extra
patches to it) to make sure that any buffers are reclaimed for VPR use,
we should be able to get rid of the CMA usage altogether and replace it
with online_pages() and offline_pages() instead. Rather than using a
fixed set of CMA areas like we currently do, each "chunk" in the VPR
driver could represent a memory block instead (which looks like it will
be 128 MiB for 4 KiB pages and 512 MiB for 64 KiB pages). We currently
use 512 MiB as the chunk size, so it should be relatively similar and
easy to adjust.

One issue that we would absolutely need this memory to be ZONE_MOVABLE
from the start. Using no-map in DT and then online_pages() probably will
not work because there's no struct page for the memory. So we're left
with keeping the memory onlined by default, in which case we'd need some
way for DT to instruct the memory to be put into ZONE_MOVABLE always.

There's a "hotpluggable" property for "memory" nodes, maybe that can be
extended to apply to reserved-memory nodes as well?

Thierry

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply

* [BUG] tracing: Too many tries to read user space
From: Jeongho Choi @ 2026-07-08 12:37 UTC (permalink / raw)
  To: linux-trace-kernel, linux-kernel, rostedt, mhiramat
  Cc: ji2yoon.jo, minki.jang, hajun.sung
In-Reply-To: <CGME20260708123754epcas2p1f15cc305ddb09f97164491d750769ef7@epcas2p1.samsung.com>

[-- Attachment #1: Type: text/plain, Size: 3535 bytes --]

Hello,

We are seeing a reproducible kernel panic related to the tracing code
when it fails to read user-space memory.

The issue was originally reported through the Android/Google Issue
Tracker, and we were advised to report it to the upstream trace mailing
list because the affected code is upstream.

Environment:

Architecture: arm64
Kernel: Linux 6.18.21
Base: Android Common Kernel (android17-6.18)
Affected area: kernel/trace/

The relevant error/panic log is:
[48916.569148] [9:           lmkd:  536] Error: Too many tries to read
user space
[48916.569156] [9:           lmkd:  536] WARNING: CPU: 9 PID: 536 at
kernel/trace/trace.c:7374 trace_user_fault_read+0x334/0x360
[48916.569443] [9:           lmkd:  536] CPU: 9 UID: 1069 PID: 536 Comm:
lmkd Tainted: G           OE       6.18.21-android17-5-ga1a8e8cab9ec-4k
#1 PREEMPT  25372cd4750dcac3c0fe86b57d47c665f97a6046

[48916.569450] [9:           lmkd:  536] pstate: 63402005 (nZCv daif
+PAN -UAO +TCO +DIT -SSBS BTYPE=--)
[48916.569452] [9:           lmkd:  536] pc :
trace_user_fault_read+0x334/0x360
[48916.569454] [9:           lmkd:  536] lr :
trace_user_fault_read+0x330/0x360
[48916.569456] [9:           lmkd:  536] sp : ffffffc096993cc0
[48916.569457] [9:           lmkd:  536] x29: ffffffc096993cd0 x28:
0000000000000065 x27: ffffff8817e45200
[48916.569461] [9:           lmkd:  536] x26: 0000000000000000 x25:
000000011616c082 x24: 0000000000000000
[48916.569463] [9:           lmkd:  536] x23: 0000000000000009 x22:
000000000000002b x21: ffffff880597f740
[48916.569466] [9:           lmkd:  536] x20: 0000007fc42dd940 x19:
ffffff8817e45770 x18: ffffffd5947ce240
[48916.569468] [9:           lmkd:  536] x17: 2073656972742079 x16:
6e616d206f6f5420 x15: 3a726f727245205d
[48916.569471] [9:           lmkd:  536] x14: 36333520203a646b x13:
6563617073207265 x12: 0000000000000001
[48916.569473] [9:           lmkd:  536] x11: 6f74207365697274 x10:
0000000000000001 x9 : 4bd9ad4516d75100
[48916.569476] [9:           lmkd:  536] x8 : 4bd9ad4516d75100 x7 :
205d383431393635 x6 : 2e36313938345b0a
[48916.569479] [9:           lmkd:  536] x5 : ffffffc080fa5998 x4 :
ffffffd591707202 x3 : 0001360a00000000
[48916.569481] [9:           lmkd:  536] x2 : ffffffc096993af4 x1 :
00000000000000c0 x0 : 0000000000000028
[48916.569484] [9:           lmkd:  536] Call trace:
[48916.569486] [9:           lmkd:  536]
trace_user_fault_read+0x334/0x360 (P)
[48916.569488] [9:           lmkd:  536]  tracing_mark_write+0x84/0x174
[48916.569491] [9:           lmkd:  536]  __arm64_sys_write+0x2a0/0x5c0
[48916.569494] [9:           lmkd:  536]  invoke_syscall+0x58/0xe4
[48916.569498] [9:           lmkd:  536]  do_el0_svc+0x48/0xdc
[48916.569500] [9:           lmkd:  536]  el0_svc+0x3c/0x98
[48916.569503] [9:           lmkd:  536]
el0t_64_sync_handler+0x20/0x130
[48916.569505] [9:           lmkd:  536]  el0t_64_sync+0x1c4/0x1c8
[48916.569508] [9:           lmkd:  536] Kernel panic - not syncing:
kernel: panic_on_warn set ...


The code at the WARN location mentioned in the log above is as follows.

7374                 if (WARN_ONCE(trys++ > 100, "Error: Too many
  tries to read user space"))
7375                         return NULL;


Our current analysis is as follows:

In the Gmail process, during a low memory situation, LMKD writes strings
to /sys/kernel/tracing/trace_marker for systrace recording. At the same
time, it broadcasts a sigkill due to low memory, which is causing the
LMKD trace marker operation to stall.


Please review this issue.
Thanks,
Jeongho Choi

[-- Attachment #2: Type: text/plain, Size: 0 bytes --]



^ permalink raw reply

* Re: [RFC 1/3] arm64: kprobes: Do not handle non-XOL faults as kprobe faults
From: Hongyan Xia @ 2026-07-08 12:12 UTC (permalink / raw)
  To: Masami Hiramatsu (Google)
  Cc: Pu Hu, ada.coupriediaz@arm.com, catalin.marinas@arm.com,
	davem@davemloft.net, Jiazi Li,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	naveen@kernel.org, will@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <20260708205947.4af54d7638f115e915e9c7ee@kernel.org>

On 7/8/2026 7:59 PM, Masami Hiramatsu wrote:
> On Wed, 8 Jul 2026 08:55:37 +0000
> Hongyan Xia <hongyan.xia@transsion.com> wrote:
>
>> On 7/8/2026 3:42 PM, Masami Hiramatsu wrote:
>>> On Wed, 8 Jul 2026 05:57:24 +0000
>>> Hongyan Xia <hongyan.xia@transsion.com> wrote:
>>>
>>>> Hi Masami,
>>>>
>>>> On 7/8/2026 8:46 AM, Masami Hiramatsu wrote:
>>>>> On Mon, 6 Jul 2026 08:36:48 +0000
>>>>> Pu Hu <hupu@transsion.com> wrote:
>>>>>
>>>>>> From: Pu Hu <hupu@transsion.com>
>>>>>>
>>>>>> kprobe_fault_handler() handles faults taken while kprobes is in
>>>>>> KPROBE_HIT_SS or KPROBE_REENTER state as faults caused by the
>>>>>> single-stepped instruction.
>>>>>>
>>>>>> That assumption is not always true. While a kprobe is preparing or
>>>>>> executing the out-of-line single-step instruction, other code may run
>>>>>> in that window. For example, perf or trace code can be invoked from the
>>>>>> debug exception path and may take a fault of its own. In that case the
>>>>>> fault did not happen on the kprobe XOL instruction, but the kprobe fault
>>>>>> handler may still try to recover it as a kprobe single-step fault.
>>>>>>
>>>>>> This can corrupt the exception recovery flow and leave the real fault to
>>>>>> be handled with a wrong PC. A typical reproducer is running simpleperf
>>>>>> with preemptirq tracepoints and dwarf callchains while a kprobe is
>>>>>> installed on a frequently executed kernel function.
>>>>>>
>>>>>> Fix this by handling faults in KPROBE_HIT_SS/KPROBE_REENTER only when
>>>>>> the faulting PC points at the current kprobe's XOL instruction. Faults
>>>>>> from any other PC are left to the normal fault handling path.
>>>>>>
>>>>>> This follows the same idea as the x86 fix in commit 6381c24cd6d5
>>>>>> ("kprobes/x86: Fix page-fault handling logic").
>>>>>>
>>>>>> Signed-off-by: Pu Hu <hupu@transsion.com>
>>>>>> Signed-off-by: Hongyan Xia <hongyan.xia@transsion.com>
>>>>>> ---
>>>>>>     arch/arm64/kernel/probes/kprobes.c | 14 ++++++++++++++
>>>>>>     1 file changed, 14 insertions(+)
>>>>>>
>>>>>> diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
>>>>>> index 43a0361a8bf0..e4d2852ce2fb 100644
>>>>>> --- a/arch/arm64/kernel/probes/kprobes.c
>>>>>> +++ b/arch/arm64/kernel/probes/kprobes.c
>>>>>> @@ -285,6 +285,20 @@ int __kprobes kprobe_fault_handler(struct pt_regs *regs, unsigned int fsr)
>>>>>>          switch (kcb->kprobe_status) {
>>>>>>          case KPROBE_HIT_SS:
>>>>>>          case KPROBE_REENTER:
>>>>>> +             /*
>>>>>> +              * A fault taken while a kprobe is single-stepping is not
>>>>>> +              * necessarily caused by the instruction in the XOL slot. For
>>>>>> +              * example, tracing or perf code running in this window may take
>>>>>> +              * an unrelated fault.
>>>>>> +              *
>>>>>> +              * Handle the fault here only when the faulting PC is the XOL
>>>>>> +              * instruction of the current kprobe. Otherwise let the normal
>>>>>> +              * fault handling path deal with it.
>>>>>> +              */
>>>>>> +             if (cur->ainsn.xol_insn &&
>>>>>> +                     instruction_pointer(regs) != (unsigned long)cur->ainsn.xol_insn)
>>>>>> +                     break;
>>>>>
>>>>> Can you check Sashiko's comments[1]?
>>>>>
>>>>> [1] https://sashiko.dev/#/patchset/20260706083636.159883-1-hupu%40transsion.com?part=1
>>>>>
>>>>> It seems that it complains about simulated kprobe's case.
>>>>> In that case, cur->ainsn.xol_insn == NULL. The simulation should be done
>>>>> in the kprobe context (which is a debug trap). I'm not sure the arm64
>>>>> can cause NMI in that context, but if it happens and causes a fault,
>>>>> it may cause a problem.
>>>>>
>>>>> So I think we can just ignore the fault on the simulated kprobes.
>>>>>
>>>>> To ensure that, you can just add:
>>>>>
>>>>>            if (cur && !cur->ainsn.xol_insn)
>>>>>                    return 0;
>>>>>
>>>>> at the entry of this function. (and remove redundant cur->ainsn.xol_insn check)
>>>>
>>>> Right, both cases:
>>>>
>>>> 1. single-step XOL
>>>> 2. simulated
>>>>
>>>> have this problem and this patch fixed 1. 2 remains unchanged.
>>>>
>>>> Ideally we should fix both, but the simulated case seems more
>>>> complicated, and at least we didn't make things worse for 2. So I wonder
>>>> if we can analyze 2 more thoroughly and fix it in a separate patch.
>>>
>>> OK, but basically this fault handler is only for the SS XOL,
>>> not for simulated one (as same as x86). So just skip the
>>> simulated case is enough in this patch.
>>> (Note that x86 also have simulated path, and that is not handled
>>> by the fault handler)
>>
>> Hmm, what happens if the original PC is a simulated instruction that has
>> a recoverable ex_table entry that handles potential page fault,
>
> That should never happen. BPF trampoline code may populate extable
> entries, but kprobe doesn't. For the simulated instruction, as far
> as I can see, there are 2 operations
>   - simulate_ldr_literal
>   - simulate_ldrsw_literal
> will involve the memory access and both are accessing PC-relative
> kernel data, which should be mapped.
>
> load_addr = addr + ldr_displacement(opcode);
> ...
> set_x_reg(regs, xn, READ_ONCE(*(u64 *)load_addr));
>
> this directly accessing the memory without using extable.
>
>
>> and this
>> PC also has an attached kprobe?
>
> You meant that putting kprobes in simulate_* functions?
> Those have __kprobes attribute, so kprobe can not probe it.
> (It should use NOKPROBE_SYMBOL...)
>
>> Then the simulated case should be able
>> to enter kprobe_fault_handler()?
>>
>> .ex_table:
>>          insn foo, handler bar
>>
>> foo:
>>          LDR symbol # Load PC-relative. Simulated. Kprobe attached.
>>          ret
>>
>> When foo is called and the kprobe fires, it enters simulated path but
>> then LDR triggers a page fault. It then enters kprobe_fault_handler() to
>> fixup the PC. Then the fixup_exception() of the normal page fault finds
>> the ex_table entry and this case is successfully handled?
>
> To do that, you need to update the simulate_ldr* to use uaccess API
> (_ASM_EXTABLE_UACCESS* macros) AND allow kprobes to probe those functions.

I see. The kprobe code actually does not even allow this combination.
The arm64 arch_prepare_kprobe() will reject extable PCs. Sorry for the
noise.

>>
>> Looks like this handler can be entered by simulated instructions? Or
>> this is only theoretical and whoever arrange code like this should be
>> fired immediately?
>
> So the simulated instructions never cause fault, or if it causes a fault
> that means the original code has a bug. (Of course there is room to
> improve the bug message so that it decodes the address probed by kprobe
> when a bug occurs.)

If we rule out the edge case I came up with (which arm64 kprobe does not
even allow to happen), then other cases are indeed kernel bugs.

Thanks for walking us through some of the logic. Will fix things in v2.

^ permalink raw reply

* Re: [RFC 1/3] arm64: kprobes: Do not handle non-XOL faults as kprobe faults
From: Masami Hiramatsu @ 2026-07-08 11:59 UTC (permalink / raw)
  To: Hongyan Xia
  Cc: Pu Hu, ada.coupriediaz@arm.com, catalin.marinas@arm.com,
	davem@davemloft.net, Jiazi Li,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	naveen@kernel.org, will@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <c33043c2-f931-4687-ad16-2d60ab12faca@transsion.com>

On Wed, 8 Jul 2026 08:55:37 +0000
Hongyan Xia <hongyan.xia@transsion.com> wrote:

> On 7/8/2026 3:42 PM, Masami Hiramatsu wrote:
> > On Wed, 8 Jul 2026 05:57:24 +0000
> > Hongyan Xia <hongyan.xia@transsion.com> wrote:
> >
> >> Hi Masami,
> >>
> >> On 7/8/2026 8:46 AM, Masami Hiramatsu wrote:
> >>> On Mon, 6 Jul 2026 08:36:48 +0000
> >>> Pu Hu <hupu@transsion.com> wrote:
> >>>
> >>>> From: Pu Hu <hupu@transsion.com>
> >>>>
> >>>> kprobe_fault_handler() handles faults taken while kprobes is in
> >>>> KPROBE_HIT_SS or KPROBE_REENTER state as faults caused by the
> >>>> single-stepped instruction.
> >>>>
> >>>> That assumption is not always true. While a kprobe is preparing or
> >>>> executing the out-of-line single-step instruction, other code may run
> >>>> in that window. For example, perf or trace code can be invoked from the
> >>>> debug exception path and may take a fault of its own. In that case the
> >>>> fault did not happen on the kprobe XOL instruction, but the kprobe fault
> >>>> handler may still try to recover it as a kprobe single-step fault.
> >>>>
> >>>> This can corrupt the exception recovery flow and leave the real fault to
> >>>> be handled with a wrong PC. A typical reproducer is running simpleperf
> >>>> with preemptirq tracepoints and dwarf callchains while a kprobe is
> >>>> installed on a frequently executed kernel function.
> >>>>
> >>>> Fix this by handling faults in KPROBE_HIT_SS/KPROBE_REENTER only when
> >>>> the faulting PC points at the current kprobe's XOL instruction. Faults
> >>>> from any other PC are left to the normal fault handling path.
> >>>>
> >>>> This follows the same idea as the x86 fix in commit 6381c24cd6d5
> >>>> ("kprobes/x86: Fix page-fault handling logic").
> >>>>
> >>>> Signed-off-by: Pu Hu <hupu@transsion.com>
> >>>> Signed-off-by: Hongyan Xia <hongyan.xia@transsion.com>
> >>>> ---
> >>>>    arch/arm64/kernel/probes/kprobes.c | 14 ++++++++++++++
> >>>>    1 file changed, 14 insertions(+)
> >>>>
> >>>> diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
> >>>> index 43a0361a8bf0..e4d2852ce2fb 100644
> >>>> --- a/arch/arm64/kernel/probes/kprobes.c
> >>>> +++ b/arch/arm64/kernel/probes/kprobes.c
> >>>> @@ -285,6 +285,20 @@ int __kprobes kprobe_fault_handler(struct pt_regs *regs, unsigned int fsr)
> >>>>         switch (kcb->kprobe_status) {
> >>>>         case KPROBE_HIT_SS:
> >>>>         case KPROBE_REENTER:
> >>>> +             /*
> >>>> +              * A fault taken while a kprobe is single-stepping is not
> >>>> +              * necessarily caused by the instruction in the XOL slot. For
> >>>> +              * example, tracing or perf code running in this window may take
> >>>> +              * an unrelated fault.
> >>>> +              *
> >>>> +              * Handle the fault here only when the faulting PC is the XOL
> >>>> +              * instruction of the current kprobe. Otherwise let the normal
> >>>> +              * fault handling path deal with it.
> >>>> +              */
> >>>> +             if (cur->ainsn.xol_insn &&
> >>>> +                     instruction_pointer(regs) != (unsigned long)cur->ainsn.xol_insn)
> >>>> +                     break;
> >>>
> >>> Can you check Sashiko's comments[1]?
> >>>
> >>> [1] https://sashiko.dev/#/patchset/20260706083636.159883-1-hupu%40transsion.com?part=1
> >>>
> >>> It seems that it complains about simulated kprobe's case.
> >>> In that case, cur->ainsn.xol_insn == NULL. The simulation should be done
> >>> in the kprobe context (which is a debug trap). I'm not sure the arm64
> >>> can cause NMI in that context, but if it happens and causes a fault,
> >>> it may cause a problem.
> >>>
> >>> So I think we can just ignore the fault on the simulated kprobes.
> >>>
> >>> To ensure that, you can just add:
> >>>
> >>>           if (cur && !cur->ainsn.xol_insn)
> >>>                   return 0;
> >>>
> >>> at the entry of this function. (and remove redundant cur->ainsn.xol_insn check)
> >>
> >> Right, both cases:
> >>
> >> 1. single-step XOL
> >> 2. simulated
> >>
> >> have this problem and this patch fixed 1. 2 remains unchanged.
> >>
> >> Ideally we should fix both, but the simulated case seems more
> >> complicated, and at least we didn't make things worse for 2. So I wonder
> >> if we can analyze 2 more thoroughly and fix it in a separate patch.
> >
> > OK, but basically this fault handler is only for the SS XOL,
> > not for simulated one (as same as x86). So just skip the
> > simulated case is enough in this patch.
> > (Note that x86 also have simulated path, and that is not handled
> > by the fault handler)
> 
> Hmm, what happens if the original PC is a simulated instruction that has
> a recoverable ex_table entry that handles potential page fault,

That should never happen. BPF trampoline code may populate extable
entries, but kprobe doesn't. For the simulated instruction, as far
as I can see, there are 2 operations 
 - simulate_ldr_literal
 - simulate_ldrsw_literal
will involve the memory access and both are accessing PC-relative
kernel data, which should be mapped.

load_addr = addr + ldr_displacement(opcode);
...
set_x_reg(regs, xn, READ_ONCE(*(u64 *)load_addr));

this directly accessing the memory without using extable.


> and this
> PC also has an attached kprobe?

You meant that putting kprobes in simulate_* functions?
Those have __kprobes attribute, so kprobe can not probe it.
(It should use NOKPROBE_SYMBOL...)

> Then the simulated case should be able
> to enter kprobe_fault_handler()?
> 
> .ex_table:
>         insn foo, handler bar
> 
> foo:
>         LDR symbol # Load PC-relative. Simulated. Kprobe attached.
>         ret
> 
> When foo is called and the kprobe fires, it enters simulated path but
> then LDR triggers a page fault. It then enters kprobe_fault_handler() to
> fixup the PC. Then the fixup_exception() of the normal page fault finds
> the ex_table entry and this case is successfully handled?

To do that, you need to update the simulate_ldr* to use uaccess API
(_ASM_EXTABLE_UACCESS* macros) AND allow kprobes to probe those functions.

> 
> Looks like this handler can be entered by simulated instructions? Or
> this is only theoretical and whoever arrange code like this should be
> fired immediately?

So the simulated instructions never cause fault, or if it causes a fault
that means the original code has a bug. (Of course there is room to
improve the bug message so that it decodes the address probed by kprobe
when a bug occurs.)

Thank you,

-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>

^ permalink raw reply

* [GIT PULL] RTLA fixes for v7.2-rc3
From: Tomas Glozar @ 2026-07-08 11:24 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Andreas Ziegler, Bastian Blank, LKML, linux-trace-kernel,
	Tomas Glozar

Steven,

The following changes since commit 8cdeaa50eae8dad34885515f62559ee83e7e8dda:

  Linux 7.2-rc2 (2026-07-05 14:44:06 -1000)

are available in the Git repository at:

  https://git.kernel.org/pub/scm/linux/kernel/git/tglozar/linux.git tags/rtla-fixes-v7.2-rc3

for you to fetch changes up to cd9993d22a577339a93dcb401574e874ea29b143:

  rtla: Also link in ctype.c (2026-07-08 10:38:18 +0200)

----------------------------------------------------------------
RTLA fixes for v7.2-rc3

- Fix missing unistd include

A missing #include <unistd.h> broke build on uclibc systems.
Add the include to fix it.

- Fix missing tools/lib/ctype.c dependency

RTLA links tools/lib/string.c as a dependency of libsubcmd, some of its
functions require _ctype. Link tools/lib/ctype.c as well, to fix build
without GCC LTO.

Signed-off-by: Tomas Glozar <tglozar@redhat.com>

----------------------------------------------------------------
Andreas Ziegler (1):
      rtla: Fix missing unistd include

Bastian Blank (1):
      rtla: Also link in ctype.c

 tools/tracing/rtla/Makefile     | 14 ++++++++++----
 tools/tracing/rtla/src/common.c |  1 +
 2 files changed, 11 insertions(+), 4 deletions(-)


^ permalink raw reply

* Re: [PATCH 2/2] spi: qcom-geni: add GENI SE registers trace event on error paths
From: Mukesh Savaliya @ 2026-07-08 10:06 UTC (permalink / raw)
  To: Praveen Talari, Bjorn Andersson, Konrad Dybcio, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Mark Brown
  Cc: linux-kernel, linux-arm-msm, linux-trace-kernel, linux-spi,
	aniket.randive, chandana.chiluveru
In-Reply-To: <20260706-add-tracepoints-for-se-reg-dump-v1-2-48bd08e28cf2@oss.qualcomm.com>



On 7/6/2026 4:38 PM, Praveen Talari wrote:
> The GENI SPI driver reports various transfer failures such as command
> timeouts, DMA reset timeouts, DMA transaction errors, and unexpected
> interrupt conditions. However, diagnosing the root cause of these
> failures is difficult as the hardware state is not captured when the
> error occurs.
> 
> Add trace_geni_se_regs() calls at critical SPI error handling paths to
> automatically capture GENI serial engine debug registers when failures
> are detected. This includes:
> 
> - M_CMD abort/cancel timeout
> - DMA TX/RX FSM reset timeout
> - DMA transaction failures and pending residue conditions
> - Unexpected interrupt error status
> - Premature transfer completion with pending TX/RX data
> 
> Dumping the SE debug registers at the time of failure provides
> additional hardware context and significantly improves post-mortem
> analysis of SPI transfer issues without affecting normal operation.
> 
> Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
> ---
>   drivers/spi/spi-geni-qcom.c | 22 ++++++++++++++++++----
>   1 file changed, 18 insertions(+), 4 deletions(-)
> 

Acked-by: Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com>

^ permalink raw reply

* Re: [PATCH v3 06/11] mm/cma: Allow dynamically creating CMA areas
From: David Hildenbrand (Arm) @ 2026-07-08  8:59 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Marek Szyprowski,
	Robin Murphy, Sumit Semwal, Benjamin Gaignard, Brian Starkey,
	John Stultz, T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding
In-Reply-To: <20260701-tegra-vpr-v3-6-d80f7b871bb4@nvidia.com>

On 7/1/26 18:08, Thierry Reding wrote:
> From: Thierry Reding <treding@nvidia.com>
> 
> There is no technical reason why there should be a limited number of CMA
> regions, so extract some code into helpers and use them to create extra
> functions (cma_create() and cma_free()) that allow creating and freeing,
> respectively, CMA regions dynamically at runtime.

I'm confused. We still allow cma_create() only during __init, right?

Would we expect callers of cma_free() after __init? Or at which point?

> 
> The static array of CMA areas cannot be replaced by dynamically created
> areas because for many of them, allocation must not fail and some cases
> may need to initialize them before the slab allocator is even available.

We can start with a memblock array of an initial size (like we do today).

Then, when you need more space, we can double the size (copying content and
exchanging the pointer). Either allocate from memblock or from slab, if
available (slab_is_available).

memblock does something similar, see memblock_double_array().


-- 
Cheers,

David

^ permalink raw reply

* Re: [RFC 1/3] arm64: kprobes: Do not handle non-XOL faults as kprobe faults
From: Hongyan Xia @ 2026-07-08  8:55 UTC (permalink / raw)
  To: Masami Hiramatsu (Google)
  Cc: Pu Hu, ada.coupriediaz@arm.com, catalin.marinas@arm.com,
	davem@davemloft.net, Jiazi Li,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	naveen@kernel.org, will@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <20260708164224.c080a1e834f02ac953007e5c@kernel.org>

On 7/8/2026 3:42 PM, Masami Hiramatsu wrote:
> On Wed, 8 Jul 2026 05:57:24 +0000
> Hongyan Xia <hongyan.xia@transsion.com> wrote:
>
>> Hi Masami,
>>
>> On 7/8/2026 8:46 AM, Masami Hiramatsu wrote:
>>> On Mon, 6 Jul 2026 08:36:48 +0000
>>> Pu Hu <hupu@transsion.com> wrote:
>>>
>>>> From: Pu Hu <hupu@transsion.com>
>>>>
>>>> kprobe_fault_handler() handles faults taken while kprobes is in
>>>> KPROBE_HIT_SS or KPROBE_REENTER state as faults caused by the
>>>> single-stepped instruction.
>>>>
>>>> That assumption is not always true. While a kprobe is preparing or
>>>> executing the out-of-line single-step instruction, other code may run
>>>> in that window. For example, perf or trace code can be invoked from the
>>>> debug exception path and may take a fault of its own. In that case the
>>>> fault did not happen on the kprobe XOL instruction, but the kprobe fault
>>>> handler may still try to recover it as a kprobe single-step fault.
>>>>
>>>> This can corrupt the exception recovery flow and leave the real fault to
>>>> be handled with a wrong PC. A typical reproducer is running simpleperf
>>>> with preemptirq tracepoints and dwarf callchains while a kprobe is
>>>> installed on a frequently executed kernel function.
>>>>
>>>> Fix this by handling faults in KPROBE_HIT_SS/KPROBE_REENTER only when
>>>> the faulting PC points at the current kprobe's XOL instruction. Faults
>>>> from any other PC are left to the normal fault handling path.
>>>>
>>>> This follows the same idea as the x86 fix in commit 6381c24cd6d5
>>>> ("kprobes/x86: Fix page-fault handling logic").
>>>>
>>>> Signed-off-by: Pu Hu <hupu@transsion.com>
>>>> Signed-off-by: Hongyan Xia <hongyan.xia@transsion.com>
>>>> ---
>>>>    arch/arm64/kernel/probes/kprobes.c | 14 ++++++++++++++
>>>>    1 file changed, 14 insertions(+)
>>>>
>>>> diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
>>>> index 43a0361a8bf0..e4d2852ce2fb 100644
>>>> --- a/arch/arm64/kernel/probes/kprobes.c
>>>> +++ b/arch/arm64/kernel/probes/kprobes.c
>>>> @@ -285,6 +285,20 @@ int __kprobes kprobe_fault_handler(struct pt_regs *regs, unsigned int fsr)
>>>>         switch (kcb->kprobe_status) {
>>>>         case KPROBE_HIT_SS:
>>>>         case KPROBE_REENTER:
>>>> +             /*
>>>> +              * A fault taken while a kprobe is single-stepping is not
>>>> +              * necessarily caused by the instruction in the XOL slot. For
>>>> +              * example, tracing or perf code running in this window may take
>>>> +              * an unrelated fault.
>>>> +              *
>>>> +              * Handle the fault here only when the faulting PC is the XOL
>>>> +              * instruction of the current kprobe. Otherwise let the normal
>>>> +              * fault handling path deal with it.
>>>> +              */
>>>> +             if (cur->ainsn.xol_insn &&
>>>> +                     instruction_pointer(regs) != (unsigned long)cur->ainsn.xol_insn)
>>>> +                     break;
>>>
>>> Can you check Sashiko's comments[1]?
>>>
>>> [1] https://sashiko.dev/#/patchset/20260706083636.159883-1-hupu%40transsion.com?part=1
>>>
>>> It seems that it complains about simulated kprobe's case.
>>> In that case, cur->ainsn.xol_insn == NULL. The simulation should be done
>>> in the kprobe context (which is a debug trap). I'm not sure the arm64
>>> can cause NMI in that context, but if it happens and causes a fault,
>>> it may cause a problem.
>>>
>>> So I think we can just ignore the fault on the simulated kprobes.
>>>
>>> To ensure that, you can just add:
>>>
>>>           if (cur && !cur->ainsn.xol_insn)
>>>                   return 0;
>>>
>>> at the entry of this function. (and remove redundant cur->ainsn.xol_insn check)
>>
>> Right, both cases:
>>
>> 1. single-step XOL
>> 2. simulated
>>
>> have this problem and this patch fixed 1. 2 remains unchanged.
>>
>> Ideally we should fix both, but the simulated case seems more
>> complicated, and at least we didn't make things worse for 2. So I wonder
>> if we can analyze 2 more thoroughly and fix it in a separate patch.
>
> OK, but basically this fault handler is only for the SS XOL,
> not for simulated one (as same as x86). So just skip the
> simulated case is enough in this patch.
> (Note that x86 also have simulated path, and that is not handled
> by the fault handler)

Hmm, what happens if the original PC is a simulated instruction that has
a recoverable ex_table entry that handles potential page fault, and this
PC also has an attached kprobe? Then the simulated case should be able
to enter kprobe_fault_handler()?

.ex_table:
        insn foo, handler bar

foo:
        LDR symbol # Load PC-relative. Simulated. Kprobe attached.
        ret

When foo is called and the kprobe fires, it enters simulated path but
then LDR triggers a page fault. It then enters kprobe_fault_handler() to
fixup the PC. Then the fixup_exception() of the normal page fault finds
the ex_table entry and this case is successfully handled?

Looks like this handler can be entered by simulated instructions? Or
this is only theoretical and whoever arrange code like this should be
fired immediately?

>
> Thank you,
>
>>
>>> Thank you,
>>>
>>>> +
>>>>                 /*
>>>>                  * We are here because the instruction being single
>>>>                  * stepped caused a page fault. We reset the current
>>>> --
>>>> 2.43.0
>>>>
>>> --
>>> Masami Hiramatsu (Google) <mhiramat@kernel.org>
>>
>
>


^ permalink raw reply

* Re: [PATCH v3 06/11] mm/cma: Allow dynamically creating CMA areas
From: David Hildenbrand (Arm) @ 2026-07-08  8:35 UTC (permalink / raw)
  To: Marek Szyprowski, Thierry Reding, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jonathan Hunter,
	Mikko Perttunen, Yury Norov, Rasmus Villemoes, Russell King,
	Alexander Gordeev, Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
	Christian Borntraeger, Sven Schnelle, Andrew Morton,
	Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Robin Murphy, Sumit Semwal,
	Benjamin Gaignard, Brian Starkey, John Stultz, T.J. Mercier,
	Christian König, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Catalin Marinas, Will Deacon
  Cc: devicetree, linux-tegra, linux-kernel, dri-devel, linux-media,
	linux-arm-kernel, linux-s390, linux-mm, iommu, linaro-mm-sig,
	linux-trace-kernel
In-Reply-To: <3f47aeab-33b1-4966-a5ce-5d6d5261e0e2@samsung.com>

On 7/7/26 12:02, Marek Szyprowski wrote:
> On 01.07.2026 18:08, Thierry Reding wrote:
>> From: Thierry Reding <treding@nvidia.com>
>>
>> There is no technical reason why there should be a limited number of CMA
>> regions, so extract some code into helpers and use them to create extra
>> functions (cma_create() and cma_free()) that allow creating and freeing,
>> respectively, CMA regions dynamically at runtime.
> 
> 
> Well, the technical reason for not creating cma regions dynamically at
> runtime is that on some architectures (like 32bit ARM) the early fixup
> for the region is needed to make it functional for DMA.

Can you point me at the code that does that? Thanks!

-- 
Cheers,

David

^ permalink raw reply

* [PATCH v2] rtla: Simplify osnoise tracer option setting code
From: Tomas Glozar @ 2026-07-08  8:30 UTC (permalink / raw)
  To: Steven Rostedt, Tomas Glozar
  Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
	Wander Lairson Costa, LKML, linux-trace-kernel

Each osnoise tracer option (in /sys/kernel/tracing/osnoise) used by RTLA
requires four functions to be defined:

- static osnoise_get_<opt>() - to get the current value of the option
  and save it into struct osnoise_context's orig_<opt> field,
- osnoise_set_<opt>() - to set the value of the option requested by the
  user after reading and saving the original with osnoise_get_<opt>(),
  and save it into <opt> field of struct osnoise_context,
- osnoise_restore_<opt>() - restore the value recorded in orig_<opt>,
- static osnoise_put_<opt>() - restore the value recorded in orig_<opt>
  and update <opt> to reflect that.

The logic is duplicated for all the options, except for cpus (which is
the only string option) and period/runtime (which are handled together
and feature extra checks).

Deduplicate by moving the common code in get/set/restore/put into common
helper functions local to osnoise.c. The original set/restore/put
functions are reduced to wrappers generated using a set of X macros:

- OSNOISE_LL_OPTIONS, which invokes the OSNOISE_LL_OPTION macro for all
  "long long" options,
- OSNOISE_FLAG_OPTIONS, which invokes the OSNOISE_FLAG_OPTION macro for
  all flag options (boolean values in osnoise/options file).

Those are also used to auto-generate the corresponding fields in struct
osnoise_context, plus initialization and restoration code for them.

There are a few minor changes in the refactored code:

- osnoise_set_<opt>() now distinguishes between -2 (option cannot be
  set) and -1 (option not present in kernel) for all options.
- OSNOISE_OPTION_INIT_VAL and OSNOISE_TIME_INIT_VAL are removed and
  replaced with a plain -1; all unsigned long long fields are changed to
  signed long long, consistent with pre-existing "%lld" format string in
  osnoise_write_ll_config().
- osnoise_restore_<opt>() now correctly resets the <opt> field (not
  orig_<opt>) for all options.

The change is intended to simplify adding and modifying tracer options.
It also makes the code shorter by a bit over 500 lines.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
v1: https://lore.kernel.org/linux-trace-kernel/20260612115121.54862-1-tglozar@redhat.com/T

v2 changes (mostly suggestions from Crystal Wood):
- Reduce code inside macros by moving as much code as possible into helper
  functions (new or existing) that take the struct field pointers as arguments.
- Remove "get" functions from the macro, as they are no longer used anywhere.
- Replace OSNOISE_OPTION_INIT_VAL and OSNOISE_TIME_INIT_VAL with a unified -1
  value for "unset" (values > INT64_MAX never worked anyway, as
  osnoise_write_ll_config() always used "%lld" to format the value).
- Add detailed comments to the helper functions, as one of the issues of the
  code was the logic being hard to understand.
- Move some details about the macros from the commit message into a comment.

 tools/tracing/rtla/src/common.h  |   96 +--
 tools/tracing/rtla/src/osnoise.c | 1093 +++++++++---------------------
 tools/tracing/rtla/src/osnoise.h |   33 +-
 3 files changed, 381 insertions(+), 841 deletions(-)

diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h
index 04b287a03f6d4..0de02347a3138 100644
--- a/tools/tracing/rtla/src/common.h
+++ b/tools/tracing/rtla/src/common.h
@@ -6,9 +6,42 @@
 #include "trace.h"
 #include "utils.h"
 
+/*
+ * OSNOISE_LL_OPTIONS - list of long long options backed by tracefs files.
+ *   OSNOISE_LL_OPTION(field_name, tracefs_path)
+ *
+ * OSNOISE_FLAG_OPTIONS - list of boolean options backed by osnoise/options.
+ *   OSNOISE_FLAG_OPTION(field_name, option_string)
+ *
+ * These X-macro lists are invoked in four places:
+ *  - struct osnoise_context field declarations (common.h),
+ *  - function declarations for osnoise_set_<opt>/osnoise_restore_<opt> (common.h),
+ *  - function definitions for set/restore/put (osnoise.c),
+ *  - context initialization and teardown in osnoise_context_alloc() and
+ *    osnoise_put_context() (osnoise.c).
+ */
+#define OSNOISE_LL_OPTIONS \
+	OSNOISE_LL_OPTION(stop_us,		"osnoise/stop_tracing_us")		\
+	OSNOISE_LL_OPTION(stop_total_us,	"osnoise/stop_tracing_total_us")	\
+	OSNOISE_LL_OPTION(print_stack,		"osnoise/print_stack")			\
+	OSNOISE_LL_OPTION(tracing_thresh,	"tracing_thresh")			\
+	OSNOISE_LL_OPTION(timerlat_period_us,	"osnoise/timerlat_period_us")		\
+	OSNOISE_LL_OPTION(timerlat_align_us,	"osnoise/timerlat_align_us")
+
+#define OSNOISE_FLAG_OPTIONS \
+	OSNOISE_FLAG_OPTION(irq_disable,	"OSNOISE_IRQ_DISABLE")	\
+	OSNOISE_FLAG_OPTION(workload,		"OSNOISE_WORKLOAD")	\
+	OSNOISE_FLAG_OPTION(timerlat_align,	"TIMERLAT_ALIGN")
+
 /*
  * osnoise_context - read, store, write, restore osnoise configs.
  */
+#define OSNOISE_LL_OPTION(name, path)			\
+	long long		orig_##name;		\
+	long long		name;
+#define OSNOISE_FLAG_OPTION(name, option_str)		\
+	int			orig_opt_##name;	\
+	int			opt_##name;
 struct osnoise_context {
 	int			flags;
 	int			ref;
@@ -16,50 +49,17 @@ struct osnoise_context {
 	char			*curr_cpus;
 	char			*orig_cpus;
 
-	/* 0 as init value */
-	unsigned long long	orig_runtime_us;
-	unsigned long long	runtime_us;
-
-	/* 0 as init value */
-	unsigned long long	orig_period_us;
-	unsigned long long	period_us;
-
-	/* 0 as init value */
-	long long		orig_timerlat_period_us;
-	long long		timerlat_period_us;
-
-	/* 0 as init value */
-	long long		orig_tracing_thresh;
-	long long		tracing_thresh;
-
-	/* -1 as init value because 0 is disabled */
-	long long		orig_stop_us;
-	long long		stop_us;
-
-	/* -1 as init value because 0 is disabled */
-	long long		orig_stop_total_us;
-	long long		stop_total_us;
-
-	/* -1 as init value because 0 is disabled */
-	long long		orig_print_stack;
-	long long		print_stack;
-
-	/* -1 as init value because 0 is off */
-	int			orig_opt_irq_disable;
-	int			opt_irq_disable;
-
-	/* -1 as init value because 0 is off */
-	int			orig_opt_workload;
-	int			opt_workload;
+	long long		orig_runtime_us;
+	long long		runtime_us;
 
-	/* -1 as init value because 0 is off */
-	int			orig_opt_timerlat_align;
-	int			opt_timerlat_align;
+	long long		orig_period_us;
+	long long		period_us;
 
-	/* 0 as init value */
-	unsigned long long	orig_timerlat_align_us;
-	unsigned long long	timerlat_align_us;
+	OSNOISE_LL_OPTIONS
+	OSNOISE_FLAG_OPTIONS
 };
+#undef OSNOISE_LL_OPTION
+#undef OSNOISE_FLAG_OPTION
 
 extern volatile int stop_tracing;
 
@@ -173,15 +173,21 @@ common_threshold_handler(const struct osnoise_tool *tool);
 int osnoise_set_cpus(struct osnoise_context *context, char *cpus);
 void osnoise_restore_cpus(struct osnoise_context *context);
 
-int osnoise_set_workload(struct osnoise_context *context, bool onoff);
+#define OSNOISE_LL_OPTION(name, path)							\
+	int osnoise_set_##name(struct osnoise_context *context, long long name);	\
+	void osnoise_restore_##name(struct osnoise_context *context);
+#define OSNOISE_FLAG_OPTION(name, option_str)					\
+	int osnoise_set_##name(struct osnoise_context *context, bool onoff);	\
+	void osnoise_restore_##name(struct osnoise_context *context);
+OSNOISE_LL_OPTIONS
+OSNOISE_FLAG_OPTIONS
+#undef OSNOISE_LL_OPTION
+#undef OSNOISE_FLAG_OPTION
 
 void osnoise_destroy_tool(struct osnoise_tool *top);
 struct osnoise_tool *osnoise_init_tool(char *tool_name);
 struct osnoise_tool *osnoise_init_trace_tool(const char *tracer);
 bool osnoise_trace_is_off(struct osnoise_tool *tool, struct osnoise_tool *record);
-int osnoise_set_stop_us(struct osnoise_context *context, long long stop_us);
-int osnoise_set_stop_total_us(struct osnoise_context *context,
-			      long long stop_total_us);
 
 int common_apply_config(struct osnoise_tool *tool, struct common_params *params);
 int top_main_loop(struct osnoise_tool *tool);
diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c
index 4ff5dad013b10..7e935b575feed 100644
--- a/tools/tracing/rtla/src/osnoise.c
+++ b/tools/tracing/rtla/src/osnoise.c
@@ -124,16 +124,40 @@ void osnoise_put_cpus(struct osnoise_context *context)
 	context->orig_cpus = NULL;
 }
 
-/*
- * osnoise_read_ll_config - read a long long value from a config
+/**
+ * osnoise_read_ll_config - read a long long value from a tracefs config
+ * @rel_path: tracefs-relative path to the config file
+ * @set_value: pointer to the cached value set by RTLA, or -1 if unset
+ * @orig_value: pointer to the cached original value read from tracefs, or -1 if unread
+ *
+ * Returns the current effective value for the config at @rel_path. If RTLA has
+ * already written a value (@set_value != -1), that value is returned. If the
+ * original has already been read (@orig_value != -1), that cached value is
+ * returned. Otherwise, reads the value from tracefs, caches it in @orig_value
+ * (so it can later be restored), and returns it.
  *
- * returns -1 on error.
+ * This is the shared read primitive used by both the manually implemented
+ * get functions (osnoise_get_runtime, osnoise_get_period) and the
+ * OSNOISE_LL_OPTION-generated set/restore/put functions.
+ *
+ * Returns the config value on success, -1 on error.
  */
-static long long osnoise_read_ll_config(char *rel_path)
+static long long osnoise_read_ll_config(char *rel_path,
+					long long *set_value,
+					long long *orig_value)
 {
 	long long retval;
 	char *buffer;
 
+	if (*set_value != -1)
+		/* option has been set by RTLA already */
+		return *set_value;
+
+	if (*orig_value != -1)
+		/* RTLA has already read the option */
+		return *orig_value;
+
+	/* current value is not known to RTLA yet, read it from tracefs */
 	buffer = tracefs_instance_file_read(NULL, rel_path, NULL);
 	if (!buffer)
 		return -1;
@@ -145,51 +169,110 @@ static long long osnoise_read_ll_config(char *rel_path)
 
 	free(buffer);
 
+	if (retval < 0)
+		goto out_err;
+
+	/* save the value and return it */
+	*orig_value = retval;
 	return retval;
+
+out_err:
+	return -1;
 }
 
-/*
- * osnoise_write_ll_config - write a long long value to a config in rel_path
+/**
+ * osnoise_write_ll_config - write a long long value to a tracefs config
+ * @rel_path: tracefs-relative path to the config file
+ * @value: the value to write
+ * @set_value: pointer to the cached value set by RTLA, or -1 if unset
+ * @orig_value: pointer to the cached original value read from tracefs, or -1 if unread
  *
- * returns -1 on error.
+ * Writes @value to the config at @rel_path. Before writing, calls
+ * osnoise_read_ll_config() to ensure the original value is cached in
+ * @orig_value (enabling later restoration). On successful write, records
+ * the new value in @set_value.
+ *
+ * This is the shared write primitive used by both the manually implemented
+ * write functions (__osnoise_write_runtime, __osnoise_write_period) and the
+ * OSNOISE_LL_OPTION-generated set/restore/put functions.
+ *
+ * Returns 0 on success, -1 on read error (option likely unknown to kernel),
+ * or -2 on write error.
  */
-static long long osnoise_write_ll_config(char *rel_path, long long value)
+static int osnoise_write_ll_config(char *rel_path,
+				   long long value,
+				   long long *set_value,
+				   long long *orig_value)
 {
-	char buffer[BUFF_U64_STR_SIZE];
+	long long curr = osnoise_read_ll_config(rel_path, set_value, orig_value);
 	long long retval;
+	char buffer[BUFF_U64_STR_SIZE];
+
+	if (curr == -1)
+		/* read failed, option likely unknown to kernel */
+		return -1;
 
 	snprintf(buffer, sizeof(buffer), "%lld\n", value);
 
 	debug_msg("setting %s to %lld\n", rel_path, value);
 
 	retval = tracefs_instance_file_write(NULL, rel_path, buffer);
-	return retval;
+
+	if (retval < 0)
+		/* write failed, hard error */
+		return -2;
+
+	/* record the set value and return success */
+	*set_value = value;
+	return 0;
 }
 
-/*
- * osnoise_get_runtime - return the original "osnoise/runtime_us" value
+/**
+ * osnoise_restore_ll_config - restore a long long config to its original value
+ * @rel_path: tracefs-relative path to the config file
+ * @set_value: pointer to the cached value set by RTLA, or -1 if unset
+ * @orig_value: pointer to the cached original value read from tracefs, or -1 if unread
  *
- * It also saves the value to be restored.
+ * Restores the config at @rel_path to the value cached in @orig_value (which
+ * was saved by a prior osnoise_read_ll_config() or osnoise_write_ll_config()
+ * call). If the original was never read, or if the current set value already
+ * matches the original, no write is performed. After restoring, clears
+ * @set_value to -1 to indicate RTLA no longer overrides this config.
+ *
+ * This is the shared restore primitive used by both the manually implemented
+ * restore function (osnoise_restore_runtime_period) and the
+ * OSNOISE_LL_OPTION-generated restore/put functions.
  */
-unsigned long long osnoise_get_runtime(struct osnoise_context *context)
+static void osnoise_restore_ll_config(char *rel_path,
+				      long long *set_value,
+				      long long *orig_value)
 {
-	long long runtime_us;
+	int retval;
 
-	if (context->runtime_us != OSNOISE_TIME_INIT_VAL)
-		return context->runtime_us;
+	if (*orig_value == -1)
+		return;
 
-	if (context->orig_runtime_us != OSNOISE_TIME_INIT_VAL)
-		return context->orig_runtime_us;
+	if (*orig_value == *set_value)
+		goto out_done;
 
-	runtime_us = osnoise_read_ll_config("osnoise/runtime_us");
-	if (runtime_us < 0)
-		goto out_err;
+	retval = osnoise_write_ll_config(rel_path, *orig_value, set_value, orig_value);
+	if (retval < 0)
+		err_msg("Could not restore original value for %s\n", rel_path);
 
-	context->orig_runtime_us = runtime_us;
-	return runtime_us;
+out_done:
+	*set_value = -1;
+}
 
-out_err:
-	return OSNOISE_TIME_INIT_VAL;
+/*
+ * osnoise_get_runtime - return the original "osnoise/runtime_us" value
+ *
+ * It also saves the value to be restored.
+ */
+long long osnoise_get_runtime(struct osnoise_context *context)
+{
+	return osnoise_read_ll_config("osnoise/runtime_us",
+				      &context->runtime_us,
+				      &context->orig_runtime_us);
 }
 
 /*
@@ -197,57 +280,29 @@ unsigned long long osnoise_get_runtime(struct osnoise_context *context)
  *
  * It also saves the value to be restored.
  */
-unsigned long long osnoise_get_period(struct osnoise_context *context)
+long long osnoise_get_period(struct osnoise_context *context)
 {
-	long long period_us;
-
-	if (context->period_us != OSNOISE_TIME_INIT_VAL)
-		return context->period_us;
-
-	if (context->orig_period_us != OSNOISE_TIME_INIT_VAL)
-		return context->orig_period_us;
-
-	period_us = osnoise_read_ll_config("osnoise/period_us");
-	if (period_us < 0)
-		goto out_err;
-
-	context->orig_period_us = period_us;
-	return period_us;
-
-out_err:
-	return OSNOISE_TIME_INIT_VAL;
+	return osnoise_read_ll_config("osnoise/period_us",
+				      &context->period_us,
+				      &context->orig_period_us);
 }
 
 static int __osnoise_write_runtime(struct osnoise_context *context,
-				   unsigned long long runtime)
+				   long long runtime)
 {
-	int retval;
-
-	if (context->orig_runtime_us == OSNOISE_TIME_INIT_VAL)
-		return -1;
-
-	retval = osnoise_write_ll_config("osnoise/runtime_us", runtime);
-	if (retval < 0)
-		return -1;
-
-	context->runtime_us = runtime;
-	return 0;
+	return osnoise_write_ll_config("osnoise/runtime_us",
+				       runtime,
+				       &context->runtime_us,
+				       &context->orig_runtime_us);
 }
 
 static int __osnoise_write_period(struct osnoise_context *context,
-				  unsigned long long period)
+				  long long period)
 {
-	int retval;
-
-	if (context->orig_period_us == OSNOISE_TIME_INIT_VAL)
-		return -1;
-
-	retval = osnoise_write_ll_config("osnoise/period_us", period);
-	if (retval < 0)
-		return -1;
-
-	context->period_us = period;
-	return 0;
+	return osnoise_write_ll_config("osnoise/period_us",
+				       period,
+				       &context->period_us,
+				       &context->orig_period_us);
 }
 
 /*
@@ -258,11 +313,11 @@ static int __osnoise_write_period(struct osnoise_context *context,
  * to set the runtime and period if they are != 0.
  */
 int osnoise_set_runtime_period(struct osnoise_context *context,
-			       unsigned long long runtime,
-			       unsigned long long period)
+			       long long runtime,
+			       long long period)
 {
-	unsigned long long curr_runtime_us;
-	unsigned long long curr_period_us;
+	long long curr_runtime_us;
+	long long curr_period_us;
 	int retval;
 
 	if (!period && !runtime)
@@ -272,7 +327,7 @@ int osnoise_set_runtime_period(struct osnoise_context *context,
 	curr_period_us = osnoise_get_period(context);
 
 	/* error getting any value? */
-	if (curr_period_us == OSNOISE_TIME_INIT_VAL || curr_runtime_us == OSNOISE_TIME_INIT_VAL)
+	if (curr_period_us == -1 || curr_runtime_us == -1)
 		return -1;
 
 	if (!period) {
@@ -309,13 +364,13 @@ int osnoise_set_runtime_period(struct osnoise_context *context,
  */
 void osnoise_restore_runtime_period(struct osnoise_context *context)
 {
-	unsigned long long orig_runtime = context->orig_runtime_us;
-	unsigned long long orig_period = context->orig_period_us;
-	unsigned long long curr_runtime = context->runtime_us;
-	unsigned long long curr_period = context->period_us;
+	long long orig_runtime = context->orig_runtime_us;
+	long long orig_period = context->orig_period_us;
+	long long curr_runtime = context->runtime_us;
+	long long curr_period = context->period_us;
 	int retval;
 
-	if ((orig_runtime == OSNOISE_TIME_INIT_VAL) && (orig_period == OSNOISE_TIME_INIT_VAL))
+	if ((orig_runtime == -1) && (orig_period == -1))
 		return;
 
 	if ((orig_period == curr_period) && (orig_runtime == curr_runtime))
@@ -326,8 +381,8 @@ void osnoise_restore_runtime_period(struct osnoise_context *context)
 		err_msg("Could not restore original osnoise runtime/period\n");
 
 out_done:
-	context->runtime_us = OSNOISE_TIME_INIT_VAL;
-	context->period_us = OSNOISE_TIME_INIT_VAL;
+	context->runtime_us = -1;
+	context->period_us = -1;
 }
 
 /*
@@ -337,498 +392,81 @@ void osnoise_put_runtime_period(struct osnoise_context *context)
 {
 	osnoise_restore_runtime_period(context);
 
-	if (context->orig_runtime_us != OSNOISE_TIME_INIT_VAL)
-		context->orig_runtime_us = OSNOISE_TIME_INIT_VAL;
-
-	if (context->orig_period_us != OSNOISE_TIME_INIT_VAL)
-		context->orig_period_us = OSNOISE_TIME_INIT_VAL;
-}
-
-/*
- * osnoise_get_timerlat_period_us - read and save the original "timerlat_period_us"
- */
-static long long
-osnoise_get_timerlat_period_us(struct osnoise_context *context)
-{
-	long long timerlat_period_us;
-
-	if (context->timerlat_period_us != OSNOISE_TIME_INIT_VAL)
-		return context->timerlat_period_us;
-
-	if (context->orig_timerlat_period_us != OSNOISE_TIME_INIT_VAL)
-		return context->orig_timerlat_period_us;
-
-	timerlat_period_us = osnoise_read_ll_config("osnoise/timerlat_period_us");
-	if (timerlat_period_us < 0)
-		goto out_err;
-
-	context->orig_timerlat_period_us = timerlat_period_us;
-	return timerlat_period_us;
-
-out_err:
-	return OSNOISE_TIME_INIT_VAL;
-}
-
-/*
- * osnoise_set_timerlat_period_us - set "timerlat_period_us"
- */
-int osnoise_set_timerlat_period_us(struct osnoise_context *context, long long timerlat_period_us)
-{
-	long long curr_timerlat_period_us = osnoise_get_timerlat_period_us(context);
-	int retval;
-
-	if (curr_timerlat_period_us == OSNOISE_TIME_INIT_VAL)
-		return -1;
-
-	retval = osnoise_write_ll_config("osnoise/timerlat_period_us", timerlat_period_us);
-	if (retval < 0)
-		return -1;
-
-	context->timerlat_period_us = timerlat_period_us;
-
-	return 0;
-}
-
-/*
- * osnoise_restore_timerlat_period_us - restore "timerlat_period_us"
- */
-void osnoise_restore_timerlat_period_us(struct osnoise_context *context)
-{
-	int retval;
-
-	if (context->orig_timerlat_period_us == OSNOISE_TIME_INIT_VAL)
-		return;
-
-	if (context->orig_timerlat_period_us == context->timerlat_period_us)
-		goto out_done;
-
-	retval = osnoise_write_ll_config("osnoise/timerlat_period_us", context->orig_timerlat_period_us);
-	if (retval < 0)
-		err_msg("Could not restore original osnoise timerlat_period_us\n");
-
-out_done:
-	context->timerlat_period_us = OSNOISE_TIME_INIT_VAL;
-}
-
-/*
- * osnoise_put_timerlat_period_us - restore original values and cleanup data
- */
-void osnoise_put_timerlat_period_us(struct osnoise_context *context)
-{
-	osnoise_restore_timerlat_period_us(context);
-
-	if (context->orig_timerlat_period_us == OSNOISE_TIME_INIT_VAL)
-		return;
-
-	context->orig_timerlat_period_us = OSNOISE_TIME_INIT_VAL;
-}
-
-/*
- * osnoise_get_timerlat_align_us - read and save the original "timerlat_align_us"
- */
-static long long
-osnoise_get_timerlat_align_us(struct osnoise_context *context)
-{
-	long long timerlat_align_us;
-
-	if (context->timerlat_align_us != OSNOISE_OPTION_INIT_VAL)
-		return context->timerlat_align_us;
-
-	if (context->orig_timerlat_align_us != OSNOISE_OPTION_INIT_VAL)
-		return context->orig_timerlat_align_us;
-
-	timerlat_align_us = osnoise_read_ll_config("osnoise/timerlat_align_us");
-	if (timerlat_align_us < 0)
-		goto out_err;
-
-	context->orig_timerlat_align_us = timerlat_align_us;
-	return timerlat_align_us;
-
-out_err:
-	return OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_set_timerlat_align_us - set "timerlat_align_us"
- */
-int osnoise_set_timerlat_align_us(struct osnoise_context *context, long long timerlat_align_us)
-{
-	long long curr_timerlat_align_us = osnoise_get_timerlat_align_us(context);
-	int retval;
-
-	if (curr_timerlat_align_us == OSNOISE_OPTION_INIT_VAL)
-		return -1;
-
-	retval = osnoise_write_ll_config("osnoise/timerlat_align_us", timerlat_align_us);
-	if (retval < 0)
-		return -1;
-
-	context->timerlat_align_us = timerlat_align_us;
-
-	return 0;
-}
-
-/*
- * osnoise_restore_timerlat_align_us - restore "timerlat_align_us"
- */
-void osnoise_restore_timerlat_align_us(struct osnoise_context *context)
-{
-	int retval;
-
-	if (context->orig_timerlat_align_us == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	if (context->orig_timerlat_align_us == context->timerlat_align_us)
-		goto out_done;
-
-	retval = osnoise_write_ll_config("osnoise/timerlat_align_us",
-				   context->orig_timerlat_align_us);
-	if (retval < 0)
-		err_msg("Could not restore original osnoise timerlat_align_us\n");
-
-out_done:
-	context->timerlat_align_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_put_timerlat_align_us - restore original values and cleanup data
- */
-void osnoise_put_timerlat_align_us(struct osnoise_context *context)
-{
-	osnoise_restore_timerlat_align_us(context);
-
-	if (context->orig_timerlat_align_us == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	context->orig_timerlat_align_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_get_stop_us - read and save the original "stop_tracing_us"
- */
-static long long
-osnoise_get_stop_us(struct osnoise_context *context)
-{
-	long long stop_us;
-
-	if (context->stop_us != OSNOISE_OPTION_INIT_VAL)
-		return context->stop_us;
-
-	if (context->orig_stop_us != OSNOISE_OPTION_INIT_VAL)
-		return context->orig_stop_us;
-
-	stop_us = osnoise_read_ll_config("osnoise/stop_tracing_us");
-	if (stop_us < 0)
-		goto out_err;
-
-	context->orig_stop_us = stop_us;
-	return stop_us;
-
-out_err:
-	return OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_set_stop_us - set "stop_tracing_us"
- */
-int osnoise_set_stop_us(struct osnoise_context *context, long long stop_us)
-{
-	long long curr_stop_us = osnoise_get_stop_us(context);
-	int retval;
-
-	if (curr_stop_us == OSNOISE_OPTION_INIT_VAL)
-		return -1;
-
-	retval = osnoise_write_ll_config("osnoise/stop_tracing_us", stop_us);
-	if (retval < 0)
-		return -1;
-
-	context->stop_us = stop_us;
-
-	return 0;
-}
-
-/*
- * osnoise_restore_stop_us - restore the original "stop_tracing_us"
- */
-void osnoise_restore_stop_us(struct osnoise_context *context)
-{
-	int retval;
-
-	if (context->orig_stop_us == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	if (context->orig_stop_us == context->stop_us)
-		goto out_done;
-
-	retval = osnoise_write_ll_config("osnoise/stop_tracing_us", context->orig_stop_us);
-	if (retval < 0)
-		err_msg("Could not restore original osnoise stop_us\n");
-
-out_done:
-	context->stop_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_put_stop_us - restore original values and cleanup data
- */
-void osnoise_put_stop_us(struct osnoise_context *context)
-{
-	osnoise_restore_stop_us(context);
-
-	if (context->orig_stop_us == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	context->orig_stop_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_get_stop_total_us - read and save the original "stop_tracing_total_us"
- */
-static long long
-osnoise_get_stop_total_us(struct osnoise_context *context)
-{
-	long long stop_total_us;
-
-	if (context->stop_total_us != OSNOISE_OPTION_INIT_VAL)
-		return context->stop_total_us;
-
-	if (context->orig_stop_total_us != OSNOISE_OPTION_INIT_VAL)
-		return context->orig_stop_total_us;
-
-	stop_total_us = osnoise_read_ll_config("osnoise/stop_tracing_total_us");
-	if (stop_total_us < 0)
-		goto out_err;
-
-	context->orig_stop_total_us = stop_total_us;
-	return stop_total_us;
-
-out_err:
-	return OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_set_stop_total_us - set "stop_tracing_total_us"
- */
-int osnoise_set_stop_total_us(struct osnoise_context *context, long long stop_total_us)
-{
-	long long curr_stop_total_us = osnoise_get_stop_total_us(context);
-	int retval;
-
-	if (curr_stop_total_us == OSNOISE_OPTION_INIT_VAL)
-		return -1;
-
-	retval = osnoise_write_ll_config("osnoise/stop_tracing_total_us", stop_total_us);
-	if (retval < 0)
-		return -1;
-
-	context->stop_total_us = stop_total_us;
-
-	return 0;
-}
-
-/*
- * osnoise_restore_stop_total_us - restore the original "stop_tracing_total_us"
- */
-void osnoise_restore_stop_total_us(struct osnoise_context *context)
-{
-	int retval;
-
-	if (context->orig_stop_total_us == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	if (context->orig_stop_total_us == context->stop_total_us)
-		goto out_done;
-
-	retval = osnoise_write_ll_config("osnoise/stop_tracing_total_us",
-			context->orig_stop_total_us);
-	if (retval < 0)
-		err_msg("Could not restore original osnoise stop_total_us\n");
-
-out_done:
-	context->stop_total_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_put_stop_total_us - restore original values and cleanup data
- */
-void osnoise_put_stop_total_us(struct osnoise_context *context)
-{
-	osnoise_restore_stop_total_us(context);
-
-	if (context->orig_stop_total_us == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	context->orig_stop_total_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_get_print_stack - read and save the original "print_stack"
- */
-static long long
-osnoise_get_print_stack(struct osnoise_context *context)
-{
-	long long print_stack;
-
-	if (context->print_stack != OSNOISE_OPTION_INIT_VAL)
-		return context->print_stack;
-
-	if (context->orig_print_stack != OSNOISE_OPTION_INIT_VAL)
-		return context->orig_print_stack;
-
-	print_stack = osnoise_read_ll_config("osnoise/print_stack");
-	if (print_stack < 0)
-		goto out_err;
-
-	context->orig_print_stack = print_stack;
-	return print_stack;
-
-out_err:
-	return OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_set_print_stack - set "print_stack"
- */
-int osnoise_set_print_stack(struct osnoise_context *context, long long print_stack)
-{
-	long long curr_print_stack = osnoise_get_print_stack(context);
-	int retval;
-
-	if (curr_print_stack == OSNOISE_OPTION_INIT_VAL)
-		return -1;
-
-	retval = osnoise_write_ll_config("osnoise/print_stack", print_stack);
-	if (retval < 0)
-		return -1;
-
-	context->print_stack = print_stack;
-
-	return 0;
-}
-
-/*
- * osnoise_restore_print_stack - restore the original "print_stack"
- */
-void osnoise_restore_print_stack(struct osnoise_context *context)
-{
-	int retval;
-
-	if (context->orig_print_stack == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	if (context->orig_print_stack == context->print_stack)
-		goto out_done;
-
-	retval = osnoise_write_ll_config("osnoise/print_stack", context->orig_print_stack);
-	if (retval < 0)
-		err_msg("Could not restore original osnoise print_stack\n");
-
-out_done:
-	context->print_stack = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_put_print_stack - restore original values and cleanup data
- */
-void osnoise_put_print_stack(struct osnoise_context *context)
-{
-	osnoise_restore_print_stack(context);
-
-	if (context->orig_print_stack == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	context->orig_print_stack = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_get_tracing_thresh - read and save the original "tracing_thresh"
- */
-static long long
-osnoise_get_tracing_thresh(struct osnoise_context *context)
-{
-	long long tracing_thresh;
-
-	if (context->tracing_thresh != OSNOISE_OPTION_INIT_VAL)
-		return context->tracing_thresh;
-
-	if (context->orig_tracing_thresh != OSNOISE_OPTION_INIT_VAL)
-		return context->orig_tracing_thresh;
-
-	tracing_thresh = osnoise_read_ll_config("tracing_thresh");
-	if (tracing_thresh < 0)
-		goto out_err;
-
-	context->orig_tracing_thresh = tracing_thresh;
-	return tracing_thresh;
-
-out_err:
-	return OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_set_tracing_thresh - set "tracing_thresh"
- */
-int osnoise_set_tracing_thresh(struct osnoise_context *context, long long tracing_thresh)
-{
-	long long curr_tracing_thresh = osnoise_get_tracing_thresh(context);
-	int retval;
-
-	if (curr_tracing_thresh == OSNOISE_OPTION_INIT_VAL)
-		return -1;
-
-	retval = osnoise_write_ll_config("tracing_thresh", tracing_thresh);
-	if (retval < 0)
-		return -1;
-
-	context->tracing_thresh = tracing_thresh;
-
-	return 0;
-}
-
-/*
- * osnoise_restore_tracing_thresh - restore the original "tracing_thresh"
- */
-void osnoise_restore_tracing_thresh(struct osnoise_context *context)
-{
-	int retval;
-
-	if (context->orig_tracing_thresh == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	if (context->orig_tracing_thresh == context->tracing_thresh)
-		goto out_done;
-
-	retval = osnoise_write_ll_config("tracing_thresh", context->orig_tracing_thresh);
-	if (retval < 0)
-		err_msg("Could not restore original tracing_thresh\n");
-
-out_done:
-	context->tracing_thresh = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_put_tracing_thresh - restore original values and cleanup data
+	if (context->orig_runtime_us != -1)
+		context->orig_runtime_us = -1;
+
+	if (context->orig_period_us != -1)
+		context->orig_period_us = -1;
+}
+
+/*
+ * Long long option set/restore/put functions, generated from OSNOISE_LL_OPTIONS.
+ */
+#define OSNOISE_LL_OPTION(name, path)						\
+int osnoise_set_##name(struct osnoise_context *context, long long name)		\
+{										\
+	return osnoise_write_ll_config(path,					\
+				       name,					\
+				       &context->name,				\
+				       &context->orig_##name);			\
+}										\
+										\
+void osnoise_restore_##name(struct osnoise_context *context)			\
+{										\
+	osnoise_restore_ll_config(path, &context->name, &context->orig_##name);	\
+}										\
+										\
+static void osnoise_put_##name(struct osnoise_context *context)			\
+{										\
+	osnoise_restore_##name(context);					\
+										\
+	if (context->orig_##name == -1)						\
+		return;								\
+										\
+	context->orig_##name = -1;						\
+}
+OSNOISE_LL_OPTIONS
+#undef OSNOISE_LL_OPTION
+
+/**
+ * osnoise_get_option - read a boolean flag from osnoise/options
+ * @option: the option name string to look for (e.g. "OSNOISE_IRQ_DISABLE")
+ * @set_value: pointer to the cached value set by RTLA, or -1 if unset
+ * @orig_value: pointer to the cached original value read from tracefs, or -1 if unread
+ *
+ * Returns the current state of the flag @option. If RTLA has already written a
+ * value (@set_value != -1), that value is returned. If the original has already
+ * been read (@orig_value != -1), that cached value is returned. Otherwise, reads
+ * the "osnoise/options" file from tracefs, checks whether @option appears with or
+ * without a "NO_" prefix, caches the result in @orig_value (so it can later be
+ * restored), and returns it.
+ *
+ * This is the shared read primitive used by the OSNOISE_FLAG_OPTION-generated
+ * set/restore/put functions.
+ *
+ * Returns 1 if enabled, 0 if disabled, or -1 on error (option unknown to kernel).
  */
-void osnoise_put_tracing_thresh(struct osnoise_context *context)
-{
-	osnoise_restore_tracing_thresh(context);
-
-	if (context->orig_tracing_thresh == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	context->orig_tracing_thresh = OSNOISE_OPTION_INIT_VAL;
-}
-
-static int osnoise_options_get_option(char *option)
+static int osnoise_get_option(char *option,
+			      int *set_value,
+			      int *orig_value)
 {
-	char *options = tracefs_instance_file_read(NULL, "osnoise/options", NULL);
+	char *options;
 	char no_option[128];
 	int retval = 0;
 	char *opt;
 
+	if (*set_value != -1)
+		/* option has been set by RTLA already */
+		return *set_value;
+
+	if (*orig_value != -1)
+		/* RTLA has already read the option */
+		return *orig_value;
+
+	/* current value is not known to RTLA yet, read it from tracefs */
+	options = tracefs_instance_file_read(NULL, "osnoise/options", NULL);
 	if (!options)
-		return OSNOISE_OPTION_INIT_VAL;
+		return -1;
 
 	/*
 	 * Check first if the option is disabled.
@@ -847,207 +485,138 @@ static int osnoise_options_get_option(char *option)
 	if (opt)
 		retval = 1;
 	else
-		retval = OSNOISE_OPTION_INIT_VAL;
+		retval = -1;
 
 out_free:
 	free(options);
-	return retval;
-}
-
-static int osnoise_options_set_option(char *option, bool onoff)
-{
-	char no_option[128];
-
-	if (onoff)
-		return tracefs_instance_file_write(NULL, "osnoise/options", option);
-
-	snprintf(no_option, sizeof(no_option), "NO_%s", option);
-
-	return tracefs_instance_file_write(NULL, "osnoise/options", no_option);
-}
-
-static int osnoise_get_irq_disable(struct osnoise_context *context)
-{
-	if (context->opt_irq_disable != OSNOISE_OPTION_INIT_VAL)
-		return context->opt_irq_disable;
-
-	if (context->orig_opt_irq_disable != OSNOISE_OPTION_INIT_VAL)
-		return context->orig_opt_irq_disable;
-
-	context->orig_opt_irq_disable = osnoise_options_get_option("OSNOISE_IRQ_DISABLE");
-
-	return context->orig_opt_irq_disable;
-}
-
-int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff)
-{
-	int opt_irq_disable = osnoise_get_irq_disable(context);
-	int retval;
-
-	if (opt_irq_disable == OSNOISE_OPTION_INIT_VAL)
-		return -1;
-
-	if (opt_irq_disable == onoff)
-		return 0;
-
-	retval = osnoise_options_set_option("OSNOISE_IRQ_DISABLE", onoff);
-	if (retval < 0)
-		return -1;
-
-	context->opt_irq_disable = onoff;
 
-	return 0;
-}
-
-static void osnoise_restore_irq_disable(struct osnoise_context *context)
-{
-	int retval;
-
-	if (context->orig_opt_irq_disable == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	if (context->orig_opt_irq_disable == context->opt_irq_disable)
-		goto out_done;
-
-	retval = osnoise_options_set_option("OSNOISE_IRQ_DISABLE", context->orig_opt_irq_disable);
 	if (retval < 0)
-		err_msg("Could not restore original OSNOISE_IRQ_DISABLE option\n");
-
-out_done:
-	context->orig_opt_irq_disable = OSNOISE_OPTION_INIT_VAL;
-}
-
-static void osnoise_put_irq_disable(struct osnoise_context *context)
-{
-	osnoise_restore_irq_disable(context);
-
-	if (context->orig_opt_irq_disable == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	context->orig_opt_irq_disable = OSNOISE_OPTION_INIT_VAL;
-}
-
-static int osnoise_get_workload(struct osnoise_context *context)
-{
-	if (context->opt_workload != OSNOISE_OPTION_INIT_VAL)
-		return context->opt_workload;
-
-	if (context->orig_opt_workload != OSNOISE_OPTION_INIT_VAL)
-		return context->orig_opt_workload;
+		goto out_err;
 
-	context->orig_opt_workload = osnoise_options_get_option("OSNOISE_WORKLOAD");
+	/* save the value and return it */
+	*orig_value = retval;
+	return retval;
 
-	return context->orig_opt_workload;
+out_err:
+	return -1;
 }
 
-int osnoise_set_workload(struct osnoise_context *context, bool onoff)
+/**
+ * osnoise_set_option - write a boolean flag to osnoise/options
+ * @option: the option name string (e.g. "OSNOISE_IRQ_DISABLE")
+ * @onoff: the desired state (true to enable, false to disable)
+ * @set_value: pointer to the cached value set by RTLA, or -1 if unset
+ * @orig_value: pointer to the cached original value read from tracefs, or -1 if unread
+ *
+ * Sets the flag @option to the state @onoff. Before writing, calls
+ * osnoise_get_option() to ensure the original value is cached in @orig_value
+ * (enabling later restoration). If the current value already matches @onoff,
+ * no write is performed. On successful write, records the new state in
+ * @set_value.
+ *
+ * This is the shared write primitive used by the OSNOISE_FLAG_OPTION-generated
+ * set/restore/put functions.
+ *
+ * Returns 0 on success, -1 on read error (option likely unknown to kernel),
+ * or -2 on write error.
+ */
+static int osnoise_set_option(char *option,
+			      bool onoff,
+			      int *set_value,
+			      int *orig_value)
 {
-	int opt_workload = osnoise_get_workload(context);
+	int curr = osnoise_get_option(option, set_value, orig_value);
+	char no_option[128];
 	int retval;
 
-	if (opt_workload == OSNOISE_OPTION_INIT_VAL)
+	if (curr == -1)
+		/* read failed, option likely unknown to kernel */
 		return -1;
 
-	if (opt_workload == onoff)
+	if (curr == onoff)
 		return 0;
 
-	retval = osnoise_options_set_option("OSNOISE_WORKLOAD", onoff);
-	if (retval < 0)
-		return -2;
-
-	context->opt_workload = onoff;
-
-	return 0;
-}
-
-static void osnoise_restore_workload(struct osnoise_context *context)
-{
-	int retval;
-
-	if (context->orig_opt_workload == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	if (context->orig_opt_workload == context->opt_workload)
-		goto out_done;
-
-	retval = osnoise_options_set_option("OSNOISE_WORKLOAD", context->orig_opt_workload);
-	if (retval < 0)
-		err_msg("Could not restore original OSNOISE_WORKLOAD option\n");
-
-out_done:
-	context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL;
-}
-
-static void osnoise_put_workload(struct osnoise_context *context)
-{
-	osnoise_restore_workload(context);
-
-	if (context->orig_opt_workload == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL;
-}
-
-static int osnoise_get_timerlat_align(struct osnoise_context *context)
-{
-	if (context->opt_timerlat_align != OSNOISE_OPTION_INIT_VAL)
-		return context->opt_timerlat_align;
-
-	if (context->orig_opt_timerlat_align != OSNOISE_OPTION_INIT_VAL)
-		return context->orig_opt_timerlat_align;
-
-	context->orig_opt_timerlat_align = osnoise_options_get_option("TIMERLAT_ALIGN");
-
-	return context->orig_opt_timerlat_align;
-}
-
-int osnoise_set_timerlat_align(struct osnoise_context *context, bool onoff)
-{
-	int opt_timerlat_align = osnoise_get_timerlat_align(context);
-	int retval;
-
-	if (opt_timerlat_align == OSNOISE_OPTION_INIT_VAL)
-		return -1;
-
-	if (opt_timerlat_align == onoff)
-		return 0;
+	if (onoff) {
+		retval = tracefs_instance_file_write(NULL, "osnoise/options", option);
+	} else {
+		snprintf(no_option, sizeof(no_option), "NO_%s", option);
+		retval = tracefs_instance_file_write(NULL, "osnoise/options", no_option);
+	}
 
-	retval = osnoise_options_set_option("TIMERLAT_ALIGN", onoff);
 	if (retval < 0)
+		/* write failed, hard error */
 		return -2;
 
-	context->opt_timerlat_align = onoff;
-
+	/* record the set value and return success */
+	*set_value = onoff;
 	return 0;
 }
 
-static void osnoise_restore_timerlat_align(struct osnoise_context *context)
+/**
+ * osnoise_restore_option - restore a boolean flag to its original value
+ * @option: the option name string (e.g. "OSNOISE_IRQ_DISABLE")
+ * @set_value: pointer to the cached value set by RTLA, or -1 if unset
+ * @orig_value: pointer to the cached original value read from tracefs, or -1 if unread
+ *
+ * Restores the flag @option to the state cached in @orig_value (which was saved
+ * by a prior osnoise_get_option() or osnoise_set_option() call). If the original
+ * was never read, or if the current set value already matches the original, no
+ * write is performed. After restoring, clears @set_value to -1 to indicate RTLA
+ * no longer overrides this option.
+ *
+ * This is the shared restore primitive used by the OSNOISE_FLAG_OPTION-generated
+ * restore/put functions.
+ */
+static void osnoise_restore_option(char *option,
+				   int *set_value,
+				   int *orig_value)
 {
 	int retval;
 
-	if (context->orig_opt_timerlat_align == OSNOISE_OPTION_INIT_VAL)
+	if (*orig_value == -1)
 		return;
 
-	if (context->orig_opt_timerlat_align == context->opt_timerlat_align)
+	if (*orig_value == *set_value)
 		goto out_done;
 
-	retval = osnoise_options_set_option("TIMERLAT_ALIGN", context->orig_opt_timerlat_align);
+	retval = osnoise_set_option(option, *orig_value, set_value, orig_value);
 	if (retval < 0)
-		err_msg("Could not restore original TIMERLAT_ALIGN option\n");
+		err_msg("Could not restore original %s option\n", option);
 
 out_done:
-	context->orig_opt_timerlat_align = OSNOISE_OPTION_INIT_VAL;
-}
-
-static void osnoise_put_timerlat_align(struct osnoise_context *context)
-{
-	osnoise_restore_timerlat_align(context);
-
-	if (context->orig_opt_timerlat_align == OSNOISE_OPTION_INIT_VAL)
-		return;
-
-	context->orig_opt_timerlat_align = OSNOISE_OPTION_INIT_VAL;
-}
+	*set_value = -1;
+}
+
+/*
+ * Flag option set/restore/put functions, generated from OSNOISE_FLAG_OPTIONS.
+ */
+#define OSNOISE_FLAG_OPTION(name, option_str)				\
+int osnoise_set_##name(struct osnoise_context *context, bool onoff)	\
+{									\
+	return osnoise_set_option(option_str,				\
+				  onoff,				\
+				  &context->opt_##name,			\
+				  &context->orig_opt_##name);		\
+}									\
+									\
+void osnoise_restore_##name(struct osnoise_context *context)		\
+{									\
+	osnoise_restore_option(option_str,				\
+			       &context->opt_##name,			\
+			       &context->orig_opt_##name);		\
+}									\
+									\
+static void osnoise_put_##name(struct osnoise_context *context)		\
+{									\
+	osnoise_restore_##name(context);				\
+									\
+	if (context->orig_opt_##name == -1)				\
+		return;							\
+									\
+	context->orig_opt_##name = -1;					\
+}
+OSNOISE_FLAG_OPTIONS
+#undef OSNOISE_FLAG_OPTION
 
 enum {
 	FLAG_CONTEXT_NEWLY_CREATED	= (1 << 0),
@@ -1083,29 +652,23 @@ struct osnoise_context *osnoise_context_alloc(void)
 
 	context = calloc_fatal(1, sizeof(*context));
 
-	context->orig_stop_us		= OSNOISE_OPTION_INIT_VAL;
-	context->stop_us		= OSNOISE_OPTION_INIT_VAL;
-
-	context->orig_stop_total_us	= OSNOISE_OPTION_INIT_VAL;
-	context->stop_total_us		= OSNOISE_OPTION_INIT_VAL;
-
-	context->orig_print_stack	= OSNOISE_OPTION_INIT_VAL;
-	context->print_stack		= OSNOISE_OPTION_INIT_VAL;
-
-	context->orig_tracing_thresh	= OSNOISE_OPTION_INIT_VAL;
-	context->tracing_thresh		= OSNOISE_OPTION_INIT_VAL;
-
-	context->orig_opt_irq_disable	= OSNOISE_OPTION_INIT_VAL;
-	context->opt_irq_disable	= OSNOISE_OPTION_INIT_VAL;
-
-	context->orig_opt_workload	= OSNOISE_OPTION_INIT_VAL;
-	context->opt_workload		= OSNOISE_OPTION_INIT_VAL;
-
-	context->orig_opt_timerlat_align	= OSNOISE_OPTION_INIT_VAL;
-	context->opt_timerlat_align		= OSNOISE_OPTION_INIT_VAL;
-
-	context->orig_timerlat_align_us	= OSNOISE_OPTION_INIT_VAL;
-	context->timerlat_align_us	= OSNOISE_OPTION_INIT_VAL;
+	/* First allocate manually implemented options... */
+	context->orig_runtime_us = -1;
+	context->runtime_us	 = -1;
+	context->orig_period_us	 = -1;
+	context->period_us	 = -1;
+
+	/* ...then the automatically generated ones. */
+#define OSNOISE_LL_OPTION(name, path)		\
+	context->orig_##name	 = -1;		\
+	context->name		 = -1;
+#define OSNOISE_FLAG_OPTION(name, option_str)	\
+	context->orig_opt_##name = -1;		\
+	context->opt_##name	 = -1;
+	OSNOISE_LL_OPTIONS
+	OSNOISE_FLAG_OPTIONS
+#undef OSNOISE_LL_OPTION
+#undef OSNOISE_FLAG_OPTION
 
 	osnoise_get_context(context);
 
@@ -1126,17 +689,17 @@ void osnoise_put_context(struct osnoise_context *context)
 	if (!(context->flags & FLAG_CONTEXT_DELETED))
 		return;
 
+	/* First restore the original values of the options... */
 	osnoise_put_cpus(context);
 	osnoise_put_runtime_period(context);
-	osnoise_put_stop_us(context);
-	osnoise_put_stop_total_us(context);
-	osnoise_put_timerlat_period_us(context);
-	osnoise_put_print_stack(context);
-	osnoise_put_tracing_thresh(context);
-	osnoise_put_irq_disable(context);
-	osnoise_put_workload(context);
-	osnoise_put_timerlat_align(context);
-	osnoise_put_timerlat_align_us(context);
+
+	/* ...then the automatically generated ones. */
+#define OSNOISE_LL_OPTION(name, path)		osnoise_put_##name(context);
+#define OSNOISE_FLAG_OPTION(name, option_str)	osnoise_put_##name(context);
+	OSNOISE_LL_OPTIONS
+	OSNOISE_FLAG_OPTIONS
+#undef OSNOISE_LL_OPTION
+#undef OSNOISE_FLAG_OPTION
 
 	free(context);
 }
diff --git a/tools/tracing/rtla/src/osnoise.h b/tools/tracing/rtla/src/osnoise.h
index 340ff5a64e6e4..6d94fc0e29b21 100644
--- a/tools/tracing/rtla/src/osnoise.h
+++ b/tools/tracing/rtla/src/osnoise.h
@@ -18,44 +18,15 @@ struct osnoise_params {
 
 #define to_osnoise_params(ptr) container_of(ptr, struct osnoise_params, common)
 
-/*
- * *_INIT_VALs are also invalid values, they are used to
- * communicate errors.
- */
-#define OSNOISE_OPTION_INIT_VAL	(-1)
-#define OSNOISE_TIME_INIT_VAL	(0)
-
 struct osnoise_context *osnoise_context_alloc(void);
 int osnoise_get_context(struct osnoise_context *context);
 void osnoise_put_context(struct osnoise_context *context);
 
 int osnoise_set_runtime_period(struct osnoise_context *context,
-			       unsigned long long runtime,
-			       unsigned long long period);
+			       long long runtime,
+			       long long period);
 void osnoise_restore_runtime_period(struct osnoise_context *context);
 
-void osnoise_restore_stop_us(struct osnoise_context *context);
-void osnoise_restore_stop_total_us(struct osnoise_context *context);
-
-int osnoise_set_timerlat_period_us(struct osnoise_context *context,
-				   long long timerlat_period_us);
-void osnoise_restore_timerlat_period_us(struct osnoise_context *context);
-
-int osnoise_set_tracing_thresh(struct osnoise_context *context,
-			       long long tracing_thresh);
-void osnoise_restore_tracing_thresh(struct osnoise_context *context);
-
-void osnoise_restore_print_stack(struct osnoise_context *context);
-int osnoise_set_print_stack(struct osnoise_context *context,
-			    long long print_stack);
-
-int osnoise_set_timerlat_align_us(struct osnoise_context *context,
-				  long long timerlat_align_us);
-void osnoise_restore_timerlat_align_us(struct osnoise_context *context);
-
-int osnoise_set_timerlat_align(struct osnoise_context *context, bool onoff);
-
-int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff);
 void osnoise_report_missed_events(struct osnoise_tool *tool);
 int osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params);
 
-- 
2.55.0


^ permalink raw reply related

* Re: [PATCH v4 RESEND 5/7] riscv: stacktrace: switch to frame-pointer based unwinder
From: Shuai Xue @ 2026-07-08  8:21 UTC (permalink / raw)
  To: Wang Han, Paul Walmsley, Palmer Dabbelt, Albert Ou
  Cc: Alexandre Ghiti, linux-riscv, Oleg Nesterov, Steven Rostedt,
	Masami Hiramatsu, Mark Rutland, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
	Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Josh Poimboeuf,
	Jiri Kosina, Miroslav Benes, Petr Mladek, Joe Lawrence,
	Shuah Khan, oliver.yang, zhuo.song, jkchen, Marcos Paulo de Souza,
	linux-kernel, linux-trace-kernel, linux-perf-users, live-patching,
	linux-kselftest
In-Reply-To: <20260629072713.3273743-6-wanghan@linux.alibaba.com>



On 6/29/26 3:27 PM, Wang Han wrote:
> Replace the open-coded frame-pointer walker in arch_stack_walk() with a
> robust kunwind state machine, modelled on arch/arm64/kernel/stacktrace.c
> and retargeted to the RISC-V {fp, ra} frame record convention. The new
> walker tracks stack bounds, consumes frame records monotonically,
> understands the metadata pt_regs records added in the previous frame
> record metadata patch, and recovers return addresses replaced by
> function graph tracing and kretprobes.
> 
> This commit introduces arch_stack_walk_reliable() but does not yet
> select HAVE_RELIABLE_STACKTRACE; that is done in a follow-up Kconfig
> patch so this commit can be reviewed and bisected as a pure unwinder
> replacement. Until that Kconfig change lands, livepatch is not yet
> enabled and arch_stack_walk_reliable() has no in-tree caller.
> 
> Three related callers are updated to keep the same frame-record
> assumptions everywhere:
> 
>    * Function graph tracing: the old RISC-V unwinder matched function
>      graph return-stack entries by the saved return-address slot. That
>      was consistent with the static mcount path, but not with the dynamic
>      ftrace path where the parent slot is ftrace_regs::ra. Use the
>      architectural frame pointer as the function graph return-address
>      cookie, matching the kunwind walker.
> 
>    * Perf callchains: route kernel callchain collection through
>      arch_stack_walk() so perf sees the same frame-pointer unwind
>      behaviour as dump_stack() and the upcoming livepatch path.
> 
>    * dump_backtrace() / __get_wchan() / show_stack(): these now go
>      through arch_stack_walk(); the explicit "Call Trace:" header is
>      moved into dump_backtrace() to preserve the original output.
> 
> The non-frame-pointer fallback walker is kept untouched for
> !CONFIG_FRAME_POINTER builds.
> 
> Signed-off-by: Wang Han <wanghan@linux.alibaba.com>
> ---
>   arch/riscv/kernel/ftrace.c         |   6 +-
>   arch/riscv/kernel/perf_callchain.c |   2 +-
>   arch/riscv/kernel/stacktrace.c     | 559 ++++++++++++++++++++++++-----
>   3 files changed, 471 insertions(+), 96 deletions(-)
> 
> diff --git a/arch/riscv/kernel/ftrace.c b/arch/riscv/kernel/ftrace.c
> index b430edfb83f4..5d55199a9230 100644
> --- a/arch/riscv/kernel/ftrace.c
> +++ b/arch/riscv/kernel/ftrace.c
> @@ -242,7 +242,8 @@ void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr,
>   	 */
>   	old = *parent;
>   
> -	if (!function_graph_enter(old, self_addr, frame_pointer, parent))
> +	if (!function_graph_enter(old, self_addr, frame_pointer,
> +				  (void *)frame_pointer))
>   		*parent = return_hooker;
>   }
>   
> @@ -264,7 +265,8 @@ void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,
>   	 */
>   	old = *parent;
>   
> -	if (!function_graph_enter_regs(old, ip, frame_pointer, parent, fregs))
> +	if (!function_graph_enter_regs(old, ip, frame_pointer,
> +				       (void *)frame_pointer, fregs))
>   		*parent = return_hooker;
>   }
>   #endif /* CONFIG_DYNAMIC_FTRACE */
> diff --git a/arch/riscv/kernel/perf_callchain.c b/arch/riscv/kernel/perf_callchain.c
> index b465bc9eb870..436af96ea59c 100644
> --- a/arch/riscv/kernel/perf_callchain.c
> +++ b/arch/riscv/kernel/perf_callchain.c
> @@ -44,5 +44,5 @@ void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry,
>   		return;
>   	}
>   
> -	walk_stackframe(NULL, regs, fill_callchain, entry);
> +	arch_stack_walk(fill_callchain, entry, NULL, regs);
>   }
> diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c
> index c7555447149b..c43bf9a84207 100644
> --- a/arch/riscv/kernel/stacktrace.c
> +++ b/arch/riscv/kernel/stacktrace.c
> @@ -11,98 +11,16 @@
>   #include <linux/sched/task_stack.h>
>   #include <linux/stacktrace.h>
>   #include <linux/ftrace.h>
> +#include <linux/kprobes.h>
> +#include <linux/llist.h>
>   
>   #include <asm/stacktrace.h>
>   
> -#ifdef CONFIG_FRAME_POINTER
> -
>   /*
> - * This disables KASAN checking when reading a value from another task's stack,
> - * since the other task could be running on another CPU and could have poisoned
> - * the stack in the meantime.
> + * Non-frame-pointer fallback unwinder.
> + * Only compiled when CONFIG_FRAME_POINTER is not enabled.
>    */
> -#define READ_ONCE_TASK_STACK(task, x)			\
> -({							\
> -	unsigned long val;				\
> -	unsigned long addr = x;				\
> -	if ((task) == current)				\
> -		val = READ_ONCE(addr);			\
> -	else						\
> -		val = READ_ONCE_NOCHECK(addr);		\
> -	val;						\
> -})
> -
> -extern asmlinkage void handle_exception(void);
> -extern unsigned long ret_from_exception_end;
> -
> -static inline int fp_is_valid(unsigned long fp, unsigned long sp)
> -{
> -	unsigned long low, high;
> -
> -	low = sp + sizeof(struct stackframe);
> -	high = ALIGN(sp, THREAD_SIZE);
> -
> -	return !(fp < low || fp > high || fp & 0x07);
> -}
> -
> -void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
> -			     bool (*fn)(void *, unsigned long), void *arg)
> -{
> -	unsigned long fp, sp, pc;
> -	int graph_idx = 0;
> -	int level = 0;
> -
> -	if (regs) {
> -		fp = frame_pointer(regs);
> -		sp = user_stack_pointer(regs);
> -		pc = instruction_pointer(regs);
> -	} else if (task == NULL || task == current) {
> -		fp = (unsigned long)__builtin_frame_address(0);
> -		sp = current_stack_pointer;
> -		pc = (unsigned long)walk_stackframe;
> -		level = -1;
> -	} else {
> -		/* task blocked in __switch_to */
> -		fp = task->thread.s[0];
> -		sp = task->thread.sp;
> -		pc = task->thread.ra;
> -	}
> -
> -	for (;;) {
> -		struct stackframe *frame;
> -
> -		if (unlikely(!__kernel_text_address(pc) || (level++ >= 0 && !fn(arg, pc))))
> -			break;
> -
> -		if (unlikely(!fp_is_valid(fp, sp)))
> -			break;
> -
> -		/* Unwind stack frame */
> -		frame = (struct stackframe *)fp - 1;
> -		sp = fp;
> -		if (regs && (regs->epc == pc) && fp_is_valid(frame->ra, sp)) {
> -			/* We hit function where ra is not saved on the stack */
> -			fp = frame->ra;
> -			pc = regs->ra;
> -		} else {
> -			fp = READ_ONCE_TASK_STACK(task, frame->fp);
> -			pc = READ_ONCE_TASK_STACK(task, frame->ra);
> -			pc = ftrace_graph_ret_addr(task, &graph_idx, pc,
> -						   &frame->ra);
> -			if (pc >= (unsigned long)handle_exception &&
> -			    pc < (unsigned long)&ret_from_exception_end) {
> -				if (unlikely(!fn(arg, pc)))
> -					break;
> -
> -				pc = ((struct pt_regs *)sp)->epc;
> -				fp = ((struct pt_regs *)sp)->s0;
> -			}
> -		}
> -
> -	}
> -}
> -
> -#else /* !CONFIG_FRAME_POINTER */
> +#ifndef CONFIG_FRAME_POINTER
>   
>   void notrace walk_stackframe(struct task_struct *task,
>   	struct pt_regs *regs, bool (*fn)(void *, unsigned long), void *arg)
> @@ -133,7 +51,12 @@ void notrace walk_stackframe(struct task_struct *task,
>   	}
>   }
>   
> -#endif /* CONFIG_FRAME_POINTER */
> +#endif /* !CONFIG_FRAME_POINTER */
> +
> +/*
> + * Common trace helpers.
> + * These are used by both the FP (kunwind) and non-FP (walk_stackframe) paths.
> + */
>   
>   static bool print_trace_address(void *arg, unsigned long pc)
>   {
> @@ -146,12 +69,12 @@ static bool print_trace_address(void *arg, unsigned long pc)
>   noinline void dump_backtrace(struct pt_regs *regs, struct task_struct *task,
>   		    const char *loglvl)
>   {
> -	walk_stackframe(task, regs, print_trace_address, (void *)loglvl);
> +	printk("%sCall Trace:\n", loglvl);
> +	arch_stack_walk(print_trace_address, (void *)loglvl, task, regs);
>   }
>   
>   void show_stack(struct task_struct *task, unsigned long *sp, const char *loglvl)
>   {
> -	pr_cont("%sCall Trace:\n", loglvl);
>   	dump_backtrace(NULL, task, loglvl);
>   }
>   
> @@ -171,17 +94,467 @@ unsigned long __get_wchan(struct task_struct *task)
>   
>   	if (!try_get_task_stack(task))
>   		return 0;
> -	walk_stackframe(task, NULL, save_wchan, &pc);
> +	arch_stack_walk(save_wchan, &pc, task, NULL);
>   	put_task_stack(task);
>   	return pc;
>   }
>   
> -noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
> -		     struct task_struct *task, struct pt_regs *regs)
> +/*
> + * Frame-pointer-based kernel unwind infrastructure.
> + * Only compiled when CONFIG_FRAME_POINTER is enabled.
> + *
> + * See: arch/arm64/kernel/stacktrace.c for the reference implementation.
> + */
> +#ifdef CONFIG_FRAME_POINTER
> +
> +/*
> + * Per-cpu stacks are only accessible when unwinding the current task in a
> + * non-preemptible context.
> + */
> +#define STACKINFO_CPU(task, name)				\
> +	({							\
> +		(((task) == current) && !preemptible())		\
> +			? stackinfo_get_##name()		\
> +			: stackinfo_get_unknown();		\
> +	})
> +
> +enum kunwind_source {
> +	KUNWIND_SOURCE_UNKNOWN,
> +	KUNWIND_SOURCE_FRAME,
> +	KUNWIND_SOURCE_CALLER,
> +	KUNWIND_SOURCE_TASK,
> +	KUNWIND_SOURCE_REGS_PC,
> +};
> +
> +union unwind_flags {
> +	unsigned long	all;
> +	struct {
> +		unsigned long	fgraph : 1,
> +				kretprobe : 1;
> +	};
> +};
> +
> +/*
> + * Kernel unwind state
> + *
> + * @common:    Common unwind state.
> + * @task:      The task being unwound.
> + * @graph_idx: Used by ftrace_graph_ret_addr() for optimized stack unwinding.
> + * @kr_cur:    When KRETPROBES is selected, holds the kretprobe instance
> + *             associated with the most recently encountered replacement ra
> + *             value.
> + */
> +struct kunwind_state {
> +	struct unwind_state common;
> +	struct task_struct *task;
> +	int graph_idx;
> +#ifdef CONFIG_KRETPROBES
> +	struct llist_node *kr_cur;
> +#endif
> +	enum kunwind_source source;
> +	union unwind_flags flags;
> +	struct pt_regs *regs;
> +};
> +
> +static __always_inline void
> +kunwind_init(struct kunwind_state *state,
> +	     struct task_struct *task)
> +{
> +	unwind_init_common(&state->common);
> +	state->task = task;
> +	state->source = KUNWIND_SOURCE_UNKNOWN;
> +	state->flags.all = 0;
> +	state->regs = NULL;
> +}
> +
> +/*
> + * Start an unwind from a pt_regs.
> + *
> + * The unwind will begin at the PC within the regs.
> + *
> + * The regs must be on a stack currently owned by the calling task.
> + */
> +static __always_inline void
> +kunwind_init_from_regs(struct kunwind_state *state,
> +		       struct pt_regs *regs)
> +{
> +	kunwind_init(state, current);
> +
> +	state->regs = regs;
> +	state->common.fp = frame_pointer(regs);
> +	state->common.pc = instruction_pointer(regs);
> +	state->source = KUNWIND_SOURCE_REGS_PC;
> +}
> +
> +/*
> + * Start an unwind from a caller.
> + *
> + * The unwind will begin at the caller of whichever function this is inlined
> + * into.
> + *
> + * The function which invokes this must be noinline.
> + */
> +static __always_inline void
> +kunwind_init_from_caller(struct kunwind_state *state)
> +{
> +	unsigned long fp = (unsigned long)__builtin_frame_address(0);
> +	struct frame_record *record = (struct frame_record *)fp - 1;
> +
> +	kunwind_init(state, current);
> +
> +	state->common.fp = READ_ONCE(record->fp);
> +	state->common.pc = READ_ONCE(record->ra);
> +	state->source = KUNWIND_SOURCE_CALLER;
> +}
> +
> +/*
> + * Start an unwind from a blocked task.
> + *
> + * The unwind will begin at the blocked task's saved PC (i.e. the caller of
> + * __switch_to).
> + *
> + * The caller should ensure the task is blocked in __switch_to for the
> + * duration of the unwind, or the unwind will be bogus. It is never valid to
> + * call this for the current task.
> + */
> +static __always_inline void
> +kunwind_init_from_task(struct kunwind_state *state,
> +		       struct task_struct *task)
> +{
> +	kunwind_init(state, task);
> +
> +	state->common.fp = task->thread.s[0];
> +	state->common.pc = task->thread.ra;
> +	state->source = KUNWIND_SOURCE_TASK;
> +}
> +
> +static __always_inline int
> +kunwind_recover_return_address(struct kunwind_state *state)
> +{
> +#ifdef CONFIG_FUNCTION_GRAPH_TRACER
> +	if (state->task->ret_stack &&
> +	    state->common.pc == (unsigned long)return_to_handler) {
> +		unsigned long orig_pc;
> +
> +		orig_pc = ftrace_graph_ret_addr(state->task, &state->graph_idx,
> +						state->common.pc,
> +						(void *)state->common.fp);
> +		if (state->common.pc == orig_pc) {
> +			WARN_ON_ONCE(state->task == current);
> +			return -EINVAL;
> +		}
> +		state->common.pc = orig_pc;
> +		state->flags.fgraph = 1;
> +	}
> +#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
> +
> +#ifdef CONFIG_KRETPROBES
> +	if (is_kretprobe_trampoline(state->common.pc)) {
> +		unsigned long orig_pc;
> +
> +		orig_pc = kretprobe_find_ret_addr(state->task,
> +						  (void *)state->common.fp,
> +						  &state->kr_cur);
> +		if (!orig_pc)
> +			return -EINVAL;
> +		state->common.pc = orig_pc;
> +		state->flags.kretprobe = 1;
> +	}
> +#endif /* CONFIG_KRETPROBES */
> +
> +	return 0;
> +}
> +
> +/*
> + * When we reach an exception boundary marked by a metadata frame record,
> + * extract pt_regs from the stack and continue unwinding from the saved
> + * context (epc and s0/fp).
> + *
> + * On RISC-V, fp points above the metadata record, so the record's
> + * frame_record portion is at fp - sizeof(struct frame_record).
> + */
> +static __always_inline int
> +kunwind_next_regs_pc(struct kunwind_state *state)
> +{
> +	struct stack_info *info;
> +	unsigned long fp = state->common.fp;
> +	struct pt_regs *regs;
> +
> +	regs = container_of((unsigned long *)(fp - sizeof(struct frame_record)),
> +			    struct pt_regs, stackframe.record.fp);
> +
> +	info = unwind_find_stack(&state->common, (unsigned long)regs,
> +				 sizeof(*regs));
> +	if (!info)
> +		return -EINVAL;
> +
> +	unwind_consume_stack(&state->common, info, (unsigned long)regs,
> +			     sizeof(*regs));
> +
> +	state->regs = regs;
> +	state->common.pc = regs->epc;
> +	state->common.fp = frame_pointer(regs);
> +	state->source = KUNWIND_SOURCE_REGS_PC;
> +	return 0;
> +}
> +
> +/*
> + * Handle a metadata frame record embedded in pt_regs.
> + *
> + * On RISC-V, fp points above the record (fp = metadata + 16), so the
> + * frame_record_meta starts at fp - sizeof(struct frame_record).
> + *
> + * FRAME_META_TYPE_FINAL: This is the outermost exception entry
> + *   (user -> kernel). Unwinding terminates successfully.
> + * FRAME_META_TYPE_PT_REGS: This is a nested exception entry
> + *   (kernel -> kernel). Continue unwinding from the saved context.
> + */
> +static __always_inline int
> +kunwind_next_frame_record_meta(struct kunwind_state *state)
> +{
> +	struct task_struct *tsk = state->task;
> +	unsigned long fp = state->common.fp;
> +	unsigned long meta_base = fp - sizeof(struct frame_record);
> +	struct frame_record_meta *meta;
> +	struct stack_info *info;
> +
> +	info = unwind_find_stack(&state->common, meta_base, sizeof(*meta));
> +	if (!info)
> +		return -EINVAL;
> +
> +	meta = (struct frame_record_meta *)meta_base;
> +	switch (READ_ONCE(meta->type)) {
> +	case FRAME_META_TYPE_FINAL:
> +		if (meta == &task_pt_regs(tsk)->stackframe)
> +			return -ENOENT;
> +		WARN_ON_ONCE(tsk == current);
> +		return -EINVAL;
> +	case FRAME_META_TYPE_PT_REGS:
> +		return kunwind_next_regs_pc(state);
> +	default:
> +		WARN_ON_ONCE(tsk == current);
> +		return -EINVAL;
> +	}
> +}
> +
> +/*
> + * Unwind from one frame record to the next.
> + *
> + * On RISC-V, the frame record sits at fp - sizeof(struct frame_record),
> + * immediately below the address pointed to by fp/s0. This applies to both
> + * normal frame records and metadata frame records (embedded in pt_regs).
> + *
> + * A metadata record is identified by both fp and ra being zero in the
> + * frame_record portion, with a type value following at fp + 16.
> + */
> +static __always_inline int
> +kunwind_next_frame_record(struct kunwind_state *state)
> +{
> +	unsigned long fp = state->common.fp;
> +	struct frame_record *record;
> +	struct stack_info *info;
> +	unsigned long new_fp, new_pc;
> +	unsigned long record_base;
> +
> +	if (fp & 0x7)
> +		return -EINVAL;
> +
> +	record_base = fp - sizeof(*record);
> +
> +	info = unwind_find_stack(&state->common, record_base, sizeof(*record));
> +	if (!info)
> +		return -EINVAL;
> +
> +	record = (struct frame_record *)record_base;
> +	new_fp = READ_ONCE(record->fp);
> +	new_pc = READ_ONCE(record->ra);
> +
> +	if (!new_fp && !new_pc)
> +		return kunwind_next_frame_record_meta(state);
> +
> +	unwind_consume_stack(&state->common, info, record_base,
> +			     sizeof(*record));
> +
> +	state->common.fp = new_fp;
> +	state->common.pc = new_pc;
> +	state->source = KUNWIND_SOURCE_FRAME;
> +
> +	return 0;
> +}
> +
> +/*
> + * Unwind from one frame record (A) to the next frame record (B).
> + *
> + * We terminate early if the location of B indicates a malformed chain of frame
> + * records (e.g. a cycle), determined based on the location and fp value of A
> + * and the location (but not the fp value) of B.
> + */
> +static __always_inline int
> +kunwind_next(struct kunwind_state *state)
> +{
> +	int err;
> +
> +	state->flags.all = 0;
> +
> +	switch (state->source) {
> +	case KUNWIND_SOURCE_FRAME:
> +	case KUNWIND_SOURCE_CALLER:
> +	case KUNWIND_SOURCE_TASK:
> +	case KUNWIND_SOURCE_REGS_PC:
> +		err = kunwind_next_frame_record(state);
> +		break;
> +	default:
> +		err = -EINVAL;
> +	}
> +
> +	if (err)
> +		return err;
> +
> +	return kunwind_recover_return_address(state);
> +}
> +
> +typedef bool (*kunwind_consume_fn)(const struct kunwind_state *state, void *cookie);
> +
> +static __always_inline int
> +do_kunwind(struct kunwind_state *state, kunwind_consume_fn consume_state,
> +	   void *cookie)
> +{
> +	int ret;
> +
> +	ret = kunwind_recover_return_address(state);
> +	if (ret)
> +		return ret;
> +
> +	while (1) {
> +		if (!consume_state(state, cookie))
> +			return -EINVAL;
> +		ret = kunwind_next(state);
> +		if (ret == -ENOENT)
> +			return 0;
> +		if (ret < 0)
> +			return ret;
> +	}
> +}
> +
> +static __always_inline int
> +kunwind_stack_walk(kunwind_consume_fn consume_state,
> +		   void *cookie, struct task_struct *task,
> +		   struct pt_regs *regs)
> +{
> +	struct task_struct *tsk = task ?: current;
> +	struct stack_info stacks[] = {
> +		stackinfo_get_task(tsk),
> +		STACKINFO_CPU(tsk, irq),
> +#ifdef CONFIG_VMAP_STACK
> +		STACKINFO_CPU(tsk, overflow),
> +#endif
> +	};
> +	struct kunwind_state state = {
> +		.common = {
> +			.stacks = stacks,
> +			.nr_stacks = ARRAY_SIZE(stacks),
> +		},
> +	};
> +
> +	if (regs) {
> +		if (tsk != current)
> +			return -EINVAL;
> +		kunwind_init_from_regs(&state, regs);
> +	} else if (tsk == current) {
> +		kunwind_init_from_caller(&state);
> +	} else {
> +		kunwind_init_from_task(&state, tsk);
> +	}
> +
> +	return do_kunwind(&state, consume_state, cookie);
> +}
> +
> +struct kunwind_consume_entry_data {
> +	stack_trace_consume_fn consume_entry;
> +	void *cookie;
> +};
> +
> +static __always_inline bool
> +arch_kunwind_consume_entry(const struct kunwind_state *state, void *cookie)
> +{
> +	struct kunwind_consume_entry_data *data = cookie;
> +
> +	return data->consume_entry(data->cookie, state->common.pc);
> +}
> +
> +static __always_inline bool
> +arch_reliable_kunwind_consume_entry(const struct kunwind_state *state, void *cookie)
> +{
> +	/*
> +	 * At an exception boundary we can reliably consume the saved PC. We do
> +	 * not know whether ra was live when the exception was taken, and
> +	 * so we cannot perform the next unwind step reliably.
> +	 *
> +	 * All that matters is whether the *entire* unwind is reliable, so give
> +	 * up as soon as we hit an exception boundary.
> +	 */
> +	if (state->source == KUNWIND_SOURCE_REGS_PC)
> +		return false;
> +
> +	return arch_kunwind_consume_entry(state, cookie);
> +}
> +
> +#endif /* CONFIG_FRAME_POINTER */
> +
> +/*
> + * arch_stack_walk - dual implementation.
> + *
> + * When CONFIG_FRAME_POINTER is enabled, uses the kunwind infrastructure for
> + * robust frame-pointer-based unwinding, consistent with arch_stack_walk_reliable.
> + *
> + * When CONFIG_FRAME_POINTER is disabled, falls back to the simple stack scan
> + * in walk_stackframe().
> + */
> +#ifdef CONFIG_FRAME_POINTER
> +
> +noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry,
> +				      void *cookie, struct task_struct *task,
> +				      struct pt_regs *regs)
> +{
> +	struct kunwind_consume_entry_data data = {
> +		.consume_entry = consume_entry,
> +		.cookie = cookie,
> +	};
> +
> +	kunwind_stack_walk(arch_kunwind_consume_entry, &data, task, regs);
> +}
> +
> +#else
> +
> +noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry,
> +				      void *cookie, struct task_struct *task,
> +				      struct pt_regs *regs)
>   {
>   	walk_stackframe(task, regs, consume_entry, cookie);
>   }
>   
> +#endif /* CONFIG_FRAME_POINTER */
> +
> +/*
> + * Reliable stack walk for livepatch (CONFIG_FRAME_POINTER only).
> + */
> +#ifdef CONFIG_FRAME_POINTER
> +
> +noinline noinstr int arch_stack_walk_reliable(stack_trace_consume_fn consume_entry,
> +					      void *cookie,
> +					      struct task_struct *task)
> +{
> +	struct kunwind_consume_entry_data data = {
> +		.consume_entry = consume_entry,
> +		.cookie = cookie,
> +	};
> +
> +	return kunwind_stack_walk(arch_reliable_kunwind_consume_entry, &data,
> +				  task, NULL);
> +}
> +
> +#endif /* CONFIG_FRAME_POINTER */
> +
>   /*
>    * Get the return address for a single stackframe and return a pointer to the
>    * next frame tail.

LGTM.

Reviewed-by: Shuai Xue <xueshuai@linux.alibaba.com>

Thanks.
Shuai


^ permalink raw reply

* Re: [PATCH v4 RESEND 2/7] riscv: stacktrace: disable KASAN and KCOV instrumentation for stacktrace.o
From: Shuai Xue @ 2026-07-08  8:07 UTC (permalink / raw)
  To: Wang Han, Paul Walmsley, Palmer Dabbelt, Albert Ou
  Cc: Alexandre Ghiti, linux-riscv, Oleg Nesterov, Steven Rostedt,
	Masami Hiramatsu, Mark Rutland, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
	Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Josh Poimboeuf,
	Jiri Kosina, Miroslav Benes, Petr Mladek, Joe Lawrence,
	Shuah Khan, oliver.yang, zhuo.song, jkchen, Marcos Paulo de Souza,
	linux-kernel, linux-trace-kernel, linux-perf-users, live-patching,
	linux-kselftest
In-Reply-To: <20260629072713.3273743-3-wanghan@linux.alibaba.com>



On 6/29/26 3:27 PM, Wang Han wrote:
> KASAN records stack traces for every alloc/free, which means it walks
> the unwinder very frequently. Instrumenting the stack trace collection
> code itself adds substantial overhead and makes the traces themselves
> noisier.
> 
> KCOV instruments every basic-block edge. The unwinder is a hot path,
> especially with KASAN enabled, so KCOV instrumentation has the same kind
> of cost and noise problem here.
> 
> Mark stacktrace.o as not KASAN- or KCOV-instrumented, matching the x86
> treatment of its stack unwinding code. RISC-V keeps the relevant unwinder
> code in stacktrace.o, so a single translation-unit annotation covers the
> equivalent scope. This is a prerequisite preference for the upcoming
> reliable unwinder, but the change is valid on its own.
> 
> Signed-off-by: Wang Han <wanghan@linux.alibaba.com>
> ---
>   arch/riscv/kernel/Makefile | 6 ++++++
>   1 file changed, 6 insertions(+)
> 
> diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
> index cabb99cadfb6..c565a72a36f3 100644
> --- a/arch/riscv/kernel/Makefile
> +++ b/arch/riscv/kernel/Makefile
> @@ -44,6 +44,12 @@ CFLAGS_REMOVE_return_address.o	= $(CC_FLAGS_FTRACE)
>   CFLAGS_REMOVE_sbi_ecall.o = $(CC_FLAGS_FTRACE)
>   endif
>   
> +# When KASAN is enabled, a stack trace is recorded for every alloc/free, which
> +# can significantly impact performance. Avoid instrumenting the stack trace
> +# collection code to minimize this impact.
> +KASAN_SANITIZE_stacktrace.o := n
> +KCOV_INSTRUMENT_stacktrace.o := n
> +
>   always-$(KBUILD_BUILTIN) += vmlinux.lds
>   
>   obj-y	+= head.o

Reviewed-by: Shuai Xue <xueshuai@linux.alibaba.com>

Thanks.
Shuai

^ permalink raw reply

* Re: [PATCH v4 RESEND 1/7] riscv: stacktrace: Add frame record metadata
From: Shuai Xue @ 2026-07-08  7:59 UTC (permalink / raw)
  To: Wang Han, Paul Walmsley, Palmer Dabbelt, Albert Ou
  Cc: Alexandre Ghiti, linux-riscv, Oleg Nesterov, Steven Rostedt,
	Masami Hiramatsu, Mark Rutland, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
	Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Josh Poimboeuf,
	Jiri Kosina, Miroslav Benes, Petr Mladek, Joe Lawrence,
	Shuah Khan, oliver.yang, zhuo.song, jkchen, Marcos Paulo de Souza,
	linux-kernel, linux-trace-kernel, linux-perf-users, live-patching,
	linux-kselftest
In-Reply-To: <20260629072713.3273743-2-wanghan@linux.alibaba.com>



On 6/29/26 3:27 PM, Wang Han wrote:
> Reliable frame-pointer unwinding needs an explicit way to identify
> exception boundaries and the final entry frame. The existing unwinder
> infers those boundaries from return addresses, which is too loose for a
> future reliable unwinder.
> 
> Add a small metadata frame record to pt_regs and initialize it on
> exception entry, kernel stack overflow, kernel thread fork, user fork,
> and early idle task setup. The record uses a zero {fp, ra} sentinel plus
> a type field so a later unwinder can distinguish a final user-to-kernel
> boundary from a nested kernel pt_regs boundary.
> 
> This follows the arm64 metadata frame-record model, adapted to the
> RISC-V {fp, ra} frame record convention.
> 
> The metadata is established at the RISC-V entry boundaries that need an
> explicit unwind marker:
> 
>    * exception entry clears the metadata {fp, ra} pair and uses SPP
>      (or MPP in M-mode) to record whether the pt_regs frame is the final
>      user-to-kernel boundary or a nested kernel boundary;
>    * the kernel stack overflow path builds a nested pt_regs metadata
>      record on the overflow stack so an unwinder can resume from the
>      pre-overflow s0 saved in PT_S0;
>    * _start_kernel builds the init task's final metadata record, while
>      the secondary CPU path sets up s0 before smp_callin() so idle-task
>      unwinding does not inherit an undefined caller frame;
>    * copy_thread creates matching final metadata records for new kernel
>      and user tasks, and keeps s0 available for the frame-pointer chain.
> 
> Keep the embedded metadata-record field offsets distinct from the
> s0-relative STACKFRAME_* offsets used by call_on_irq_stack(), because
> the latter describe a frame record relative to s0 rather than to the
> record base.
> 
> These changes keep s0 reserved for the frame-pointer chain at task and
> exception boundaries.


Overall the patch looks good to me. One nit below:

> 
> Signed-off-by: Wang Han <wanghan@linux.alibaba.com>
> ---
>   arch/riscv/include/asm/ptrace.h           |  9 ++++
>   arch/riscv/include/asm/stacktrace/frame.h | 53 +++++++++++++++++++++++
>   arch/riscv/kernel/asm-offsets.c           |  6 +++
>   arch/riscv/kernel/entry.S                 | 39 ++++++++++++++++-
>   arch/riscv/kernel/head.S                  | 23 ++++++++++
>   arch/riscv/kernel/process.c               | 33 +++++++++++++-
>   6 files changed, 159 insertions(+), 4 deletions(-)
>   create mode 100644 arch/riscv/include/asm/stacktrace/frame.h
> 
> diff --git a/arch/riscv/include/asm/ptrace.h b/arch/riscv/include/asm/ptrace.h
> index addc8188152f..4b9b0f279214 100644
> --- a/arch/riscv/include/asm/ptrace.h
> +++ b/arch/riscv/include/asm/ptrace.h
> @@ -8,6 +8,7 @@
>   
>   #include <uapi/asm/ptrace.h>
>   #include <asm/csr.h>
> +#include <asm/stacktrace/frame.h>
>   #include <linux/compiler.h>
>   
>   #ifndef __ASSEMBLER__
> @@ -53,6 +54,14 @@ struct pt_regs {
>   	unsigned long cause;
>   	/* a0 value before the syscall */
>   	unsigned long orig_a0;
> +
> +	/*
> +	 * This frame record is entirely zeroed on exception entry, allowing the
> +	 * unwinder to identify exception boundaries. The type field encodes
> +	 * whether the exception was taken from user (FINAL) or kernel (PT_REGS)
> +	 * mode.
> +	 */
> +	struct frame_record_meta stackframe;
>   };
>   
>   #define PTRACE_SYSEMU			0x1f
> diff --git a/arch/riscv/include/asm/stacktrace/frame.h b/arch/riscv/include/asm/stacktrace/frame.h
> new file mode 100644
> index 000000000000..5720a6c65fe8
> --- /dev/null
> +++ b/arch/riscv/include/asm/stacktrace/frame.h
> @@ -0,0 +1,53 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __ASM_RISCV_STACKTRACE_FRAME_H
> +#define __ASM_RISCV_STACKTRACE_FRAME_H
> +
> +/*
> + * See: arch/arm64/include/asm/stacktrace/frame.h for the reference
> + * implementation.
> + */
> +
> +/*
> + * - FRAME_META_TYPE_NONE
> + *
> + *   This value is reserved.
> + *
> + * - FRAME_META_TYPE_FINAL
> + *
> + *   The record is the last entry on the stack.
> + *   Unwinding should terminate successfully.
> + *
> + * - FRAME_META_TYPE_PT_REGS
> + *
> + *   The record is embedded within a struct pt_regs, recording the registers at
> + *   an arbitrary point in time.
> + *   Unwinding should consume pt_regs::epc, followed by pt_regs::ra.
> + *
> + * Note: all other values are reserved and should result in unwinding
> + * terminating with an error.
> + */
> +#define FRAME_META_TYPE_NONE		0
> +#define FRAME_META_TYPE_FINAL		1
> +#define FRAME_META_TYPE_PT_REGS		2
> +
> +#ifndef __ASSEMBLER__
> +/*
> + * A standard RISC-V frame record.
> + */
> +struct frame_record {
> +	unsigned long fp;
> +	unsigned long ra;
> +};
> +
> +/*
> + * A metadata frame record indicating a special unwind.
> + * The record::{fp,ra} fields must be zero to indicate the presence of
> + * metadata.
> + */
> +struct frame_record_meta {
> +	struct frame_record record;
> +	unsigned long type;
> +};
> +#endif /* __ASSEMBLER__ */
> +
> +#endif /* __ASM_RISCV_STACKTRACE_FRAME_H */
> diff --git a/arch/riscv/kernel/asm-offsets.c b/arch/riscv/kernel/asm-offsets.c
> index a75f0cfea1e9..bc8e8cd7130a 100644
> --- a/arch/riscv/kernel/asm-offsets.c
> +++ b/arch/riscv/kernel/asm-offsets.c
> @@ -131,6 +131,9 @@ void asm_offsets(void)
>   	OFFSET(PT_BADADDR, pt_regs, badaddr);
>   	OFFSET(PT_CAUSE, pt_regs, cause);
>   
> +	DEFINE(S_STACKFRAME,		offsetof(struct pt_regs, stackframe));
> +	DEFINE(S_STACKFRAME_TYPE,	offsetof(struct pt_regs, stackframe.type));
> +
>   	OFFSET(SUSPEND_CONTEXT_REGS, suspend_context, regs);
>   
>   	OFFSET(HIBERN_PBE_ADDR, pbe, address);
> @@ -503,6 +506,9 @@ void asm_offsets(void)
>   	DEFINE(STACKFRAME_SIZE_ON_STACK, ALIGN(sizeof(struct stackframe), STACK_ALIGN));
>   	DEFINE(STACKFRAME_FP, offsetof(struct stackframe, fp) - sizeof(struct stackframe));
>   	DEFINE(STACKFRAME_RA, offsetof(struct stackframe, ra) - sizeof(struct stackframe));
> +	DEFINE(STACKFRAME_RECORD_SIZE, sizeof(struct stackframe));

This should be sizeof(struct frame_record) rather than
sizeof(struct stackframe). STACKFRAME_RECORD_SIZE is used to skip past
the embedded frame_record_meta.record field:

     addi s0, sp, S_STACKFRAME + STACKFRAME_RECORD_SIZE

The two structs happen to be identical today, but they are independent
types serving different purposes — struct stackframe is the existing
unwinder frame record, while struct frame_record is the one embedded
in struct frame_record_meta introduced by this patch. If struct
stackframe ever gains an extra field, this offset would silently break.

With that fixed:

Reviewed-by: Shuai Xue <xueshuai@linux.alibaba.com>

Thanks.
Shuai

^ permalink raw reply

* [PATCH v2 4/4] KVM: arm64: Add hyp_printk event to nVHE/pKVM hyp
From: Vincent Donnefort @ 2026-07-08  7:54 UTC (permalink / raw)
  To: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
	catalin.marinas, will, rostedt
  Cc: linux-arm-kernel, kvmarm, kernel-team, qperret, tabba,
	linux-trace-kernel, Vincent Donnefort
In-Reply-To: <20260708075435.47419-1-vdonnefort@google.com>

Create an event to allow developers to log pretty much anything into the
hypervisor tracing buffer with trace_hyp_printk(), just like the kernel
tracing has the function trace_printk().

  trace_hyp_printk("foobar");
  trace_hyp_printk("foo=%d", foo);
  trace_hyp_printk("foo=%d bar=0x%016llx", foo, bar);

To ensure writing into the tracing buffer is fast, store the string
format into a kernel-accessible ELF section. The hypervisor only has to
write into the event the string ID, which is the delta between the
hyp_string_fmt and the start of the ELF section.

The string format is parsed by the kernel. It is therefore not possible
to dereference hypervisor pointers and the format specifier %s is not
supported. Also the %p specifiers will fail to find the hypervisor
function.

To not waste tracing buffer data, use a dynamic size. Each
argument is 8 bytes and the in-kernel printing function can simply know
how many of them there are by looking at the event length.

Reviewed-by: Fuad Tabba <tabba@google.com>
Tested-by: Fuad Tabba <tabba@google.com>
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>

diff --git a/arch/arm64/include/asm/kvm_hypevents.h b/arch/arm64/include/asm/kvm_hypevents.h
index 743c49bd878f..8465b523cb1d 100644
--- a/arch/arm64/include/asm/kvm_hypevents.h
+++ b/arch/arm64/include/asm/kvm_hypevents.h
@@ -57,4 +57,18 @@ HYP_EVENT(selftest,
 	),
 	RE_PRINTK("id=%llu", __entry->id)
 );
+
+/*
+ * trace_hyp_printk() has too many specificities to be declared with HYP_EVENT().
+ * However, we can use a REMOTE_EVENT macro to automatically do the declaration
+ * for the kernel side.
+ */
+REMOTE_EVENT_CUSTOM_PRINTK(hyp_printk,
+	0, /* id will be overwritten during hyp event init */
+	RE_STRUCT(
+		re_field(u16, fmt_id)
+		re_field(u64, args[])
+	),
+	__hyp_trace_printk(evt, seq)
+);
 #endif
diff --git a/arch/arm64/include/asm/kvm_hyptrace.h b/arch/arm64/include/asm/kvm_hyptrace.h
index de133b735f72..46097105fdd8 100644
--- a/arch/arm64/include/asm/kvm_hyptrace.h
+++ b/arch/arm64/include/asm/kvm_hyptrace.h
@@ -23,4 +23,12 @@ extern struct remote_event __hyp_events_end[];
 extern struct hyp_event_id __hyp_event_ids_start[];
 extern struct hyp_event_id __hyp_event_ids_end[];
 
+#define HYP_STRING_FMT_MAX_SIZE 128
+
+struct hyp_string_fmt {
+	const char	fmt[HYP_STRING_FMT_MAX_SIZE];
+};
+
+extern struct hyp_string_fmt __hyp_string_fmts_start[];
+extern struct hyp_string_fmt __hyp_string_fmts_end[];
 #endif
diff --git a/arch/arm64/kernel/image-vars.h b/arch/arm64/kernel/image-vars.h
index d4c7d45ae6bc..ec03621d7a81 100644
--- a/arch/arm64/kernel/image-vars.h
+++ b/arch/arm64/kernel/image-vars.h
@@ -141,6 +141,7 @@ KVM_NVHE_ALIAS(__hyp_rodata_end);
 #ifdef CONFIG_NVHE_EL2_TRACING
 KVM_NVHE_ALIAS(__hyp_event_ids_start);
 KVM_NVHE_ALIAS(__hyp_event_ids_end);
+KVM_NVHE_ALIAS(__hyp_string_fmts_start);
 #endif
 
 /* pKVM static key */
diff --git a/arch/arm64/kernel/vmlinux.lds.S b/arch/arm64/kernel/vmlinux.lds.S
index af1d72020976..7f9b6c7dfe70 100644
--- a/arch/arm64/kernel/vmlinux.lds.S
+++ b/arch/arm64/kernel/vmlinux.lds.S
@@ -324,6 +324,10 @@ SECTIONS
 		__hyp_events_start = .;
 		*(SORT(_hyp_events.*))
 		__hyp_events_end = .;
+
+		__hyp_string_fmts_start = .;
+		*(_hyp_string_fmts)
+		__hyp_string_fmts_end = .;
 	}
 #endif
 	/*
diff --git a/arch/arm64/kvm/hyp/include/nvhe/define_events.h b/arch/arm64/kvm/hyp/include/nvhe/define_events.h
index 776d4c6cb702..370e8c2d39fe 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/define_events.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/define_events.h
@@ -10,5 +10,3 @@
 #define HYP_EVENT_MULTI_READ
 #include <asm/kvm_hypevents.h>
 #undef HYP_EVENT_MULTI_READ
-
-#undef HYP_EVENT
diff --git a/arch/arm64/kvm/hyp/include/nvhe/trace.h b/arch/arm64/kvm/hyp/include/nvhe/trace.h
index 8813ff250f8e..3d0b5c634bb3 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/trace.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/trace.h
@@ -46,6 +46,69 @@ static inline pid_t __tracing_get_vcpu_pid(struct kvm_cpu_context *host_ctxt)
 void *tracing_reserve_entry(unsigned long length);
 void tracing_commit_entry(void);
 
+/*
+ * The trace_hyp_printk boilerplate is too fiddly to be declared with
+ * HYP_EVENT():
+ *
+ * The string format is stored into a kernel-accessible ELF section. The
+ * hypervisor only writes the format ID.
+ *
+ * The function has a variadic prototype. We have no easy way to know each
+ * argument width so they must all cast to u64.
+ */
+#define REMOTE_EVENT_CUSTOM_PRINTK(...)
+
+#define __TO_U64_0()
+#define __TO_U64_1(x)			, (u64)(x)
+#define __TO_U64_2(x, ...)		, (u64)(x) __TO_U64_1(__VA_ARGS__)
+#define __TO_U64_3(x, ...)		, (u64)(x) __TO_U64_2(__VA_ARGS__)
+#define __TO_U64_4(x, ...)		, (u64)(x) __TO_U64_3(__VA_ARGS__)
+#define __TO_U64_5(x, ...)		, (u64)(x) __TO_U64_4(__VA_ARGS__)
+#define __TO_U64_6(x, ...)		, (u64)(x) __TO_U64_5(__VA_ARGS__)
+#define __TO_U64_7(x, ...)		, (u64)(x) __TO_U64_6(__VA_ARGS__)
+#define __TO_U64_8(x, ...)		, (u64)(x) __TO_U64_7(__VA_ARGS__)
+
+#define __TO_U64_X(N, ...)		CONCATENATE(__TO_U64_, N)(__VA_ARGS__)
+#define __TO_U64(...)			__TO_U64_X(COUNT_ARGS(__VA_ARGS__), ##__VA_ARGS__)
+
+REMOTE_EVENT_FORMAT(hyp_printk, HE_STRUCT(he_field(u16, fmt_id) he_field(u64, args[])));
+extern struct hyp_event_id hyp_event_id_hyp_printk;
+
+static __always_inline void __trace_hyp_printk(struct hyp_string_fmt *fmt, int nr_args, ...)
+{
+	struct remote_event_format_hyp_printk *entry;
+	va_list va;
+	int i;
+
+	if (!atomic_read(&hyp_event_id_hyp_printk.enabled))
+		return;
+
+	entry = tracing_reserve_entry(struct_size(entry, args, nr_args));
+	if (!entry)
+		return;
+
+	entry->hdr.id = hyp_event_id_hyp_printk.id;
+	entry->fmt_id = fmt - __hyp_string_fmts_start;
+
+	va_start(va, nr_args);
+	for (i = 0; i < nr_args; i++)
+		entry->args[i] = va_arg(va, u64);
+	va_end(va);
+
+	tracing_commit_entry();
+}
+
+
+#define trace_hyp_printk(__fmt, __args...)						\
+do {											\
+	static struct hyp_string_fmt __used __section("_hyp_string_fmts") fmt = {	\
+		.fmt = __fmt								\
+	};										\
+	BUILD_BUG_ON(sizeof(__fmt) > HYP_STRING_FMT_MAX_SIZE);				\
+	/* __TO_U64 prepends a comma if there are arguments */				\
+	__trace_hyp_printk(&fmt, COUNT_ARGS(__args) __TO_U64(__args));			\
+} while (0)
+
 int __tracing_load(unsigned long desc_va, size_t desc_size);
 void __tracing_unload(void);
 int __tracing_enable(bool enable);
@@ -58,6 +121,8 @@ static inline void *tracing_reserve_entry(unsigned long length) { return NULL; }
 static inline void tracing_commit_entry(void) { }
 #define HYP_EVENT(__name, __proto, __struct, __assign, __printk)      \
 	static inline void trace_##__name(__proto) {}
+#define REMOTE_EVENT_CUSTOM_PRINTK(...)
+#define trace_hyp_printk(fmt, args...) do { } while (0)
 
 static inline int __tracing_load(unsigned long desc_va, size_t desc_size) { return -ENODEV; }
 static inline void __tracing_unload(void) { }
diff --git a/arch/arm64/kvm/hyp/nvhe/events.c b/arch/arm64/kvm/hyp/nvhe/events.c
index add9383aadb5..12223d2e3618 100644
--- a/arch/arm64/kvm/hyp/nvhe/events.c
+++ b/arch/arm64/kvm/hyp/nvhe/events.c
@@ -9,6 +9,12 @@
 
 #include <nvhe/define_events.h>
 
+/*
+ * The hyp_printk event is not declared with HYP_EVENT in kvm_hypevents.h,
+ * so we manually add the boilerplate here.
+ */
+HYP_EVENT(hyp_printk, 0, 0, 0, 0);
+
 int __tracing_enable_event(unsigned short id, bool enable)
 {
 	struct hyp_event_id *event_id = &__hyp_event_ids_start[id];
diff --git a/arch/arm64/kvm/hyp_trace.c b/arch/arm64/kvm/hyp_trace.c
index 0c7349fe006a..f39a2451c1bc 100644
--- a/arch/arm64/kvm/hyp_trace.c
+++ b/arch/arm64/kvm/hyp_trace.c
@@ -391,6 +391,9 @@ static struct trace_remote_callbacks trace_remote_callbacks = {
 
 static const char *__hyp_enter_exit_reason_str(u8 reason);
 
+struct remote_event_format_hyp_printk;
+static void __hyp_trace_printk(struct remote_event_format_hyp_printk *entry, struct trace_seq *seq);
+
 #include "define_hypevents.h"
 
 static const char *__hyp_enter_exit_reason_str(u8 reason)
@@ -409,6 +412,61 @@ static const char *__hyp_enter_exit_reason_str(u8 reason)
 	return strs[min(reason, HYP_REASON_UNKNOWN)];
 }
 
+static void __hyp_trace_printk(struct remote_event_format_hyp_printk *entry, struct trace_seq *seq)
+{
+	const char *fmt = (const char *)(&__hyp_string_fmts_start[entry->fmt_id]);
+	struct ring_buffer_event *evt = (void *)entry - RB_EVNT_HDR_SIZE;
+	int nr_args;
+
+	trace_seq_putc(seq, ' ');
+
+	if ((void *)fmt >= (void *)__hyp_string_fmts_end) {
+		trace_seq_printf(seq, "Unknown hyp_string_fmt ID %d\n", entry->fmt_id);
+		return;
+	}
+
+	nr_args = (ring_buffer_event_length(evt) -
+		   offsetof(struct remote_event_format_hyp_printk, args)) / sizeof(entry->args[0]);
+	switch (nr_args) {
+	case 0:
+		trace_seq_printf(seq, fmt);
+		break;
+	case 1:
+		trace_seq_printf(seq, fmt, entry->args[0]);
+		break;
+	case 2:
+		trace_seq_printf(seq, fmt, entry->args[0], entry->args[1]);
+		break;
+	case 3:
+		trace_seq_printf(seq, fmt, entry->args[0], entry->args[1], entry->args[2]);
+		break;
+	case 4:
+		trace_seq_printf(seq, fmt, entry->args[0], entry->args[1], entry->args[2],
+				 entry->args[3]);
+		break;
+	case 5:
+		trace_seq_printf(seq, fmt, entry->args[0], entry->args[1], entry->args[2],
+				 entry->args[3], entry->args[4]);
+		break;
+	case 6:
+		trace_seq_printf(seq, fmt, entry->args[0], entry->args[1], entry->args[2],
+				 entry->args[3], entry->args[4], entry->args[5]);
+		break;
+	case 7:
+		trace_seq_printf(seq, fmt, entry->args[0], entry->args[1], entry->args[2],
+				 entry->args[3], entry->args[4], entry->args[5], entry->args[6]);
+		break;
+	default:
+		trace_seq_printf(seq, fmt, entry->args[0], entry->args[1],
+				 entry->args[2], entry->args[3], entry->args[4], entry->args[5],
+				 entry->args[6], entry->args[7]);
+		break;
+	}
+
+	if (seq->buffer[trace_seq_used(seq) - 1] != '\n')
+		trace_seq_putc(seq, '\n');
+}
+
 static void __init hyp_trace_init_events(void)
 {
 	struct hyp_event_id *hyp_event_id = __hyp_event_ids_start;
@@ -418,6 +476,9 @@ static void __init hyp_trace_init_events(void)
 	/* Events on both sides hypervisor are sorted */
 	for (; event < __hyp_events_end; event++, hyp_event_id++, id++)
 		event->id = hyp_event_id->id = id;
+
+	WARN(__hyp_string_fmts_end - __hyp_string_fmts_start > U16_MAX + 1,
+	     "Too many trace_hyp_printk() callsites\n");
 }
 
 int __init kvm_hyp_trace_init(void)
-- 
2.55.0.795.g602f6c329a-goog


^ permalink raw reply related

* [PATCH v2 3/4] tracing/remotes: Add REMOTE_EVENT_CUSTOM_PRINTK() helper
From: Vincent Donnefort @ 2026-07-08  7:54 UTC (permalink / raw)
  To: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
	catalin.marinas, will, rostedt
  Cc: linux-arm-kernel, kvmarm, kernel-team, qperret, tabba,
	linux-trace-kernel, Vincent Donnefort
In-Reply-To: <20260708075435.47419-1-vdonnefort@google.com>

The current REMOTE_EVENT() takes as a __printk argument a string format
and a list of arguments, such as RE_STRUCT("foo=%d bar=%d", foo, bar).
Add a REMOTE_EVENT_CUSTOM_PRINTK() where the __printk argument can be a
function. This intends to support the creation of a "printk" event for
the arm64 nVHE/pKVM hypervisor with a dynamic prototype and by extension
a dynamic print format.

Reviewed-by: Fuad Tabba <tabba@google.com>
Tested-by: Fuad Tabba <tabba@google.com>
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>

diff --git a/include/trace/define_remote_events.h b/include/trace/define_remote_events.h
index 676e803dc144..4f4d58e37b84 100644
--- a/include/trace/define_remote_events.h
+++ b/include/trace/define_remote_events.h
@@ -35,17 +35,26 @@ do {											\
 
 #define RE_PRINTK(__args...) __args
 
-#define REMOTE_EVENT(__name, __id, __struct, __printk)					\
-	REMOTE_EVENT_FORMAT(__name, __struct);						\
+#define REMOTE_EVENT_PRINT_FUNC(__name, __printk)					\
 	static void remote_event_print_##__name(void *evt, struct trace_seq *seq)	\
 	{										\
 		struct remote_event_format_##__name __maybe_unused *__entry = evt;	\
 		trace_seq_puts(seq, #__name);						\
-		remote_printk(__printk);						\
+		__printk;								\
 	}
+
+#define REMOTE_EVENT(__name, __id, __struct, __printk)					\
+	REMOTE_EVENT_FORMAT(__name, __struct);						\
+	REMOTE_EVENT_PRINT_FUNC(__name, remote_printk(__printk))
+
+#define REMOTE_EVENT_CUSTOM_PRINTK(__name, __id, __struct, __printk)			\
+	REMOTE_EVENT_FORMAT(__name, __struct);						\
+	REMOTE_EVENT_PRINT_FUNC(__name, __printk)
+
 #include REMOTE_EVENT_INCLUDE(REMOTE_EVENT_INCLUDE_FILE)
 
 #undef REMOTE_EVENT
+#undef REMOTE_EVENT_CUSTOM_PRINTK
 #undef RE_PRINTK
 #undef re_field
 #define re_field(__type, __field)							\
@@ -70,4 +79,8 @@ do {											\
 		.print_fmt	= remote_event_print_fmt_##__name,			\
 		.print		= remote_event_print_##__name,				\
 	}
+
+#define REMOTE_EVENT_CUSTOM_PRINTK(__name, __id, __struct, __printk)			\
+	REMOTE_EVENT(__name, __id, RE_STRUCT(__struct), "Unknown")
+
 #include REMOTE_EVENT_INCLUDE(REMOTE_EVENT_INCLUDE_FILE)
-- 
2.55.0.795.g602f6c329a-goog


^ permalink raw reply related

* [PATCH v2 2/4] KVM: arm64: Move kvm_define_hypevents.h to arch/arm64/kvm/
From: Vincent Donnefort @ 2026-07-08  7:54 UTC (permalink / raw)
  To: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
	catalin.marinas, will, rostedt
  Cc: linux-arm-kernel, kvmarm, kernel-team, qperret, tabba,
	linux-trace-kernel, Vincent Donnefort
In-Reply-To: <20260708075435.47419-1-vdonnefort@google.com>

kvm_define_hypevents.h is used to define the kernel-side structures for
hypervisor events. It doesn't need to be used anywhere else than in
hyp_trace.c.

Rename and move it to arch/arm64/kvm/

Reviewed-by: Fuad Tabba <tabba@google.com>
Tested-by: Fuad Tabba <tabba@google.com>
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>

diff --git a/arch/arm64/include/asm/kvm_define_hypevents.h b/arch/arm64/kvm/define_hypevents.h
similarity index 100%
rename from arch/arm64/include/asm/kvm_define_hypevents.h
rename to arch/arm64/kvm/define_hypevents.h
diff --git a/arch/arm64/kvm/hyp_trace.c b/arch/arm64/kvm/hyp_trace.c
index 2411b4c32932..0c7349fe006a 100644
--- a/arch/arm64/kvm/hyp_trace.c
+++ b/arch/arm64/kvm/hyp_trace.c
@@ -391,7 +391,7 @@ static struct trace_remote_callbacks trace_remote_callbacks = {
 
 static const char *__hyp_enter_exit_reason_str(u8 reason);
 
-#include <asm/kvm_define_hypevents.h>
+#include "define_hypevents.h"
 
 static const char *__hyp_enter_exit_reason_str(u8 reason)
 {
-- 
2.55.0.795.g602f6c329a-goog


^ permalink raw reply related

* [PATCH v2 0/4] trace_hyp_printk() for pKVM/nVHE hypervisor
From: Vincent Donnefort @ 2026-07-08  7:54 UTC (permalink / raw)
  To: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
	catalin.marinas, will, rostedt
  Cc: linux-arm-kernel, kvmarm, kernel-team, qperret, tabba,
	linux-trace-kernel, Vincent Donnefort

Hi all,

This series adds a hypervisor event "hyp_printk" which enables
developers to log pretty much anything into the hypervisor tracing
buffer, just like the kernel function trace_printk().

This enables rich logging from the hypervisor, while leaving all the
string parsing burden to the kernel. This has been the main way of
debugging pKVM in Android.

Even though not strictly related to trace_hyp_printk, I have added the
following two patches:

  * KVM: arm64: Allow early calls to pKVM host_share/unshare_hyp

    This one mainly intends to support one of the new features I have
    posted here [1], which allows to enable tracing as early as
    possible. I have added it here to limit cross-posting.

  * KVM: arm64: Move kvm_define_hypevents.h to arch/arm64/kvm/

    This one is just a cleanup.

[1] https://lore.kernel.org/all/20260605163825.1762953-1-vdonnefort@google.com/

Changelog:

v2:
  - Collect Fuad's tags
  - Rebase on 7.2-rc2
  - Warn when trace_hyp_printk callsites are exhausted (Fuad)

v1 (https://lore.kernel.org/all/20260612142245.1015744-1-vdonnefort@google.com/)

Vincent Donnefort (4):
  KVM: arm64: Allow early calls to pKVM host_share/unshare_hyp
  KVM: arm64: Move kvm_define_hypevents.h to arch/arm64/kvm/
  tracing/remotes: Add REMOTE_EVENT_CUSTOM_PRINTK() helper
  KVM: arm64: Add hyp_printk event to nVHE/pKVM hyp

 arch/arm64/include/asm/kvm_asm.h              |  4 +-
 arch/arm64/include/asm/kvm_hypevents.h        | 14 ++++
 arch/arm64/include/asm/kvm_hyptrace.h         |  8 +++
 arch/arm64/kernel/image-vars.h                |  1 +
 arch/arm64/kernel/vmlinux.lds.S               |  4 ++
 .../define_hypevents.h}                       |  0
 .../kvm/hyp/include/nvhe/define_events.h      |  2 -
 arch/arm64/kvm/hyp/include/nvhe/trace.h       | 65 +++++++++++++++++++
 arch/arm64/kvm/hyp/nvhe/events.c              |  6 ++
 arch/arm64/kvm/hyp/nvhe/hyp-main.c            |  2 +-
 arch/arm64/kvm/hyp_trace.c                    | 63 +++++++++++++++++-
 include/trace/define_remote_events.h          | 19 +++++-
 12 files changed, 179 insertions(+), 9 deletions(-)
 rename arch/arm64/{include/asm/kvm_define_hypevents.h => kvm/define_hypevents.h} (100%)


base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
-- 
2.55.0.795.g602f6c329a-goog


^ permalink raw reply

* [PATCH v2 1/4] KVM: arm64: Allow early calls to pKVM host_share/unshare_hyp
From: Vincent Donnefort @ 2026-07-08  7:54 UTC (permalink / raw)
  To: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
	catalin.marinas, will, rostedt
  Cc: linux-arm-kernel, kvmarm, kernel-team, qperret, tabba,
	linux-trace-kernel, Vincent Donnefort
In-Reply-To: <20260708075435.47419-1-vdonnefort@google.com>

The hypervisor tracing for pKVM relies on the __pkvm_host_share_hyp and
__pkvm_host_unshare_hyp HVCs. In order to start tracing as early as
possible, allow those two HVCs before the host is deprivileged.

Reviewed-by: Fuad Tabba <tabba@google.com>
Tested-by: Fuad Tabba <tabba@google.com>
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>

diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index 043495f7fc78..fb049c40d04f 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -89,12 +89,12 @@ enum __kvm_host_smccc_func {
 	__KVM_HOST_SMCCC_FUNC___vgic_v3_restore_vmcr_aprs,
 	__KVM_HOST_SMCCC_FUNC___vgic_v5_save_apr,
 	__KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_apr,
+	__KVM_HOST_SMCCC_FUNC___pkvm_host_share_hyp,
+	__KVM_HOST_SMCCC_FUNC___pkvm_host_unshare_hyp,
 
 	MARKER(__KVM_HOST_SMCCC_FUNC_PKVM_ONLY),
 
 	/* Hypercalls that are available only when pKVM has finalised. */
-	__KVM_HOST_SMCCC_FUNC___pkvm_host_share_hyp,
-	__KVM_HOST_SMCCC_FUNC___pkvm_host_unshare_hyp,
 	__KVM_HOST_SMCCC_FUNC___pkvm_host_donate_guest,
 	__KVM_HOST_SMCCC_FUNC___pkvm_host_share_guest,
 	__KVM_HOST_SMCCC_FUNC___pkvm_host_unshare_guest,
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index d3c69de698f4..361dbfe2e832 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -740,9 +740,9 @@ static const hcall_t host_hcall[] = {
 	HANDLE_FUNC(__vgic_v3_restore_vmcr_aprs),
 	HANDLE_FUNC(__vgic_v5_save_apr),
 	HANDLE_FUNC(__vgic_v5_restore_vmcr_apr),
-
 	HANDLE_FUNC(__pkvm_host_share_hyp),
 	HANDLE_FUNC(__pkvm_host_unshare_hyp),
+
 	HANDLE_FUNC(__pkvm_host_donate_guest),
 	HANDLE_FUNC(__pkvm_host_share_guest),
 	HANDLE_FUNC(__pkvm_host_unshare_guest),
-- 
2.55.0.795.g602f6c329a-goog


^ permalink raw reply related

* Re: [RFC 1/3] arm64: kprobes: Do not handle non-XOL faults as kprobe faults
From: Masami Hiramatsu @ 2026-07-08  7:42 UTC (permalink / raw)
  To: Hongyan Xia
  Cc: Pu Hu, ada.coupriediaz@arm.com, catalin.marinas@arm.com,
	davem@davemloft.net, Jiazi Li,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	naveen@kernel.org, will@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <8cc07468-804e-4ee1-acad-77bc99af74c6@transsion.com>

On Wed, 8 Jul 2026 05:57:24 +0000
Hongyan Xia <hongyan.xia@transsion.com> wrote:

> Hi Masami,
> 
> On 7/8/2026 8:46 AM, Masami Hiramatsu wrote:
> > On Mon, 6 Jul 2026 08:36:48 +0000
> > Pu Hu <hupu@transsion.com> wrote:
> >
> >> From: Pu Hu <hupu@transsion.com>
> >>
> >> kprobe_fault_handler() handles faults taken while kprobes is in
> >> KPROBE_HIT_SS or KPROBE_REENTER state as faults caused by the
> >> single-stepped instruction.
> >>
> >> That assumption is not always true. While a kprobe is preparing or
> >> executing the out-of-line single-step instruction, other code may run
> >> in that window. For example, perf or trace code can be invoked from the
> >> debug exception path and may take a fault of its own. In that case the
> >> fault did not happen on the kprobe XOL instruction, but the kprobe fault
> >> handler may still try to recover it as a kprobe single-step fault.
> >>
> >> This can corrupt the exception recovery flow and leave the real fault to
> >> be handled with a wrong PC. A typical reproducer is running simpleperf
> >> with preemptirq tracepoints and dwarf callchains while a kprobe is
> >> installed on a frequently executed kernel function.
> >>
> >> Fix this by handling faults in KPROBE_HIT_SS/KPROBE_REENTER only when
> >> the faulting PC points at the current kprobe's XOL instruction. Faults
> >> from any other PC are left to the normal fault handling path.
> >>
> >> This follows the same idea as the x86 fix in commit 6381c24cd6d5
> >> ("kprobes/x86: Fix page-fault handling logic").
> >>
> >> Signed-off-by: Pu Hu <hupu@transsion.com>
> >> Signed-off-by: Hongyan Xia <hongyan.xia@transsion.com>
> >> ---
> >>   arch/arm64/kernel/probes/kprobes.c | 14 ++++++++++++++
> >>   1 file changed, 14 insertions(+)
> >>
> >> diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
> >> index 43a0361a8bf0..e4d2852ce2fb 100644
> >> --- a/arch/arm64/kernel/probes/kprobes.c
> >> +++ b/arch/arm64/kernel/probes/kprobes.c
> >> @@ -285,6 +285,20 @@ int __kprobes kprobe_fault_handler(struct pt_regs *regs, unsigned int fsr)
> >>        switch (kcb->kprobe_status) {
> >>        case KPROBE_HIT_SS:
> >>        case KPROBE_REENTER:
> >> +             /*
> >> +              * A fault taken while a kprobe is single-stepping is not
> >> +              * necessarily caused by the instruction in the XOL slot. For
> >> +              * example, tracing or perf code running in this window may take
> >> +              * an unrelated fault.
> >> +              *
> >> +              * Handle the fault here only when the faulting PC is the XOL
> >> +              * instruction of the current kprobe. Otherwise let the normal
> >> +              * fault handling path deal with it.
> >> +              */
> >> +             if (cur->ainsn.xol_insn &&
> >> +                     instruction_pointer(regs) != (unsigned long)cur->ainsn.xol_insn)
> >> +                     break;
> >
> > Can you check Sashiko's comments[1]?
> >
> > [1] https://sashiko.dev/#/patchset/20260706083636.159883-1-hupu%40transsion.com?part=1
> >
> > It seems that it complains about simulated kprobe's case.
> > In that case, cur->ainsn.xol_insn == NULL. The simulation should be done
> > in the kprobe context (which is a debug trap). I'm not sure the arm64
> > can cause NMI in that context, but if it happens and causes a fault,
> > it may cause a problem.
> >
> > So I think we can just ignore the fault on the simulated kprobes.
> >
> > To ensure that, you can just add:
> >
> >          if (cur && !cur->ainsn.xol_insn)
> >                  return 0;
> >
> > at the entry of this function. (and remove redundant cur->ainsn.xol_insn check)
> 
> Right, both cases:
> 
> 1. single-step XOL
> 2. simulated
> 
> have this problem and this patch fixed 1. 2 remains unchanged.
> 
> Ideally we should fix both, but the simulated case seems more
> complicated, and at least we didn't make things worse for 2. So I wonder
> if we can analyze 2 more thoroughly and fix it in a separate patch.

OK, but basically this fault handler is only for the SS XOL,
not for simulated one (as same as x86). So just skip the
simulated case is enough in this patch.
(Note that x86 also have simulated path, and that is not handled
by the fault handler)

Thank you,


> 
> > Thank you,
> >
> >> +
> >>                /*
> >>                 * We are here because the instruction being single
> >>                 * stepped caused a page fault. We reset the current
> >> --
> >> 2.43.0
> >>
> > --
> > Masami Hiramatsu (Google) <mhiramat@kernel.org>
> 


-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>

^ permalink raw reply

* Re: [RFC 2/3] arm64: kprobes: Allow reentering kprobes while single-stepping
From: Hongyan Xia @ 2026-07-08  7:12 UTC (permalink / raw)
  To: Masami Hiramatsu (Google), Pu Hu
  Cc: ada.coupriediaz@arm.com, catalin.marinas@arm.com,
	davem@davemloft.net, Jiazi Li,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	naveen@kernel.org, will@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <20260708095400.7582bc9589fd9da925aa9c1d@kernel.org>

On 7/8/2026 8:54 AM, Masami Hiramatsu wrote:
> On Mon, 6 Jul 2026 08:36:49 +0000
> Pu Hu <hupu@transsion.com> wrote:
>
>> From: Pu Hu <hupu@transsion.com>
>>
>> A kprobe can be hit while another kprobe is in KPROBE_HIT_SS state. This
>> can happen when tracing or perf code runs from the debug exception path
>> while the first kprobe is preparing or executing its out-of-line
>> single-step instruction.
>>
>> Currently arm64 treats a kprobe hit in KPROBE_HIT_SS as unrecoverable,
>> the same as a hit in KPROBE_REENTER. This is too strict. A hit in
>> KPROBE_HIT_SS is still a one-level reentry and can be handled by saving
>> the current kprobe state and setting up single-step for the new probe,
>> just like reentry from KPROBE_HIT_ACTIVE or KPROBE_HIT_SSDONE.
>>
>> The truly unrecoverable case is hitting another kprobe while already in
>> KPROBE_REENTER, because the reentry save area has already been consumed.
>>
>> Move KPROBE_HIT_SS to the recoverable reentry cases and leave
>> KPROBE_REENTER as the unrecoverable nested reentry case.
>>
>> This mirrors the x86 fix in commit 6a5022a56ac3
>> ("kprobes/x86: Allow to handle reentered kprobe on single-stepping").
>
> Can you also check the Sashiko comment?
>
> https://sashiko.dev/#/patchset/20260706083636.159883-1-hupu%40transsion.com?part=2
>
> This seems indicating potentially brakage of reenter kprobes on arm64.
> But is it possible to hit another kprobe while SS on arm64? It is
> the same question about the previous one, can NMI happens during
> the single stepping? (maybe yes, because it is non-maskable)

It is possible as this is what we hit. There are
preempt_enable/disable() calls during SS which can trigger perf events
sampling the user stack, which may then trigger page faults and send the
CPU into one more level of exception context. I believe this is what you
saw in 6a5022a56ac3?
> Anyway, for making it safer, we need to add saved_irqflags to prev_kprobe.

Yes, this seems to be a legit bug that needs to be fixed. Thank you.

> Thank you,
>
>>
>> Signed-off-by: Pu Hu <hupu@transsion.com>
>> Signed-off-by: Hongyan Xia <hongyan.xia@transsion.com>
>> ---
>>   arch/arm64/kernel/probes/kprobes.c | 8 +++++++-
>>   1 file changed, 7 insertions(+), 1 deletion(-)
>>
>> diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
>> index e4d2852ce2fb..764b2228cca0 100644
>> --- a/arch/arm64/kernel/probes/kprobes.c
>> +++ b/arch/arm64/kernel/probes/kprobes.c
>> @@ -240,10 +240,16 @@ static int __kprobes reenter_kprobe(struct kprobe *p,
>>        switch (kcb->kprobe_status) {
>>        case KPROBE_HIT_SSDONE:
>>        case KPROBE_HIT_ACTIVE:
>> +     case KPROBE_HIT_SS:
>> +             /*
>> +              * A probe can be hit while another kprobe is preparing or
>> +              * executing its XOL single-step instruction. This is still a
>> +              * recoverable one-level reentry, so handle it in the same way as
>> +              * reentry from KPROBE_HIT_ACTIVE or KPROBE_HIT_SSDONE.
>> +              */
>>                kprobes_inc_nmissed_count(p);
>>                setup_singlestep(p, regs, kcb, 1);
>>                break;
>> -     case KPROBE_HIT_SS:
>>        case KPROBE_REENTER:
>>                pr_warn("Failed to recover from reentered kprobes.\n");
>>                dump_kprobe(p);
>> --
>> 2.43.0
>>
>
>
> --
> Masami Hiramatsu (Google) <mhiramat@kernel.org>


^ permalink raw reply

* Re: [PATCH v4 RESEND 4/7] riscv: stacktrace: introduce stack-bound tracking helpers
From: Shuai Xue @ 2026-07-08  6:59 UTC (permalink / raw)
  To: Wang Han, Paul Walmsley, Palmer Dabbelt, Albert Ou
  Cc: Alexandre Ghiti, linux-riscv, Oleg Nesterov, Steven Rostedt,
	Masami Hiramatsu, Mark Rutland, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, Namhyung Kim, Alexander Shishkin,
	Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Josh Poimboeuf,
	Jiri Kosina, Miroslav Benes, Petr Mladek, Joe Lawrence,
	Shuah Khan, oliver.yang, zhuo.song, jkchen, Marcos Paulo de Souza,
	linux-kernel, linux-trace-kernel, linux-perf-users, live-patching,
	linux-kselftest
In-Reply-To: <20260629072713.3273743-5-wanghan@linux.alibaba.com>



On 6/29/26 3:27 PM, Wang Han wrote:
> A reliable unwinder needs to validate that every frame record it reads
> is fully contained in a known kernel stack, and it needs to refuse to
> walk back into a stack it has already left. Add the building blocks
> for that:
> 
>    * struct stack_info / struct unwind_state in a new
>      asm/stacktrace/common.h, modelled on the arm64 reference
>      implementation.
>    * stackinfo_get_irq() / stackinfo_get_task() / stackinfo_get_overflow()
>      plus the corresponding on_*_stack() predicates in asm/stacktrace.h,
>      so callers can ask "is this object on stack X?" by stack kind
>      rather than open-coded address arithmetic.
>    * unwind_init_common(), unwind_find_stack() and
>      unwind_consume_stack() helpers that enforce the
>      forward-progress-only invariant required for reliability.
> 
> No existing user is wired up to these helpers in this commit; the
> unwinder switch comes in a follow-up. The header changes leave
> on_thread_stack() with the same semantics as before, just expressed in
> terms of the new helpers.
> 
> Reviewed-by: Shuai Xue <xueshuai@linux.alibaba.com>
> Signed-off-by: Wang Han <wanghan@linux.alibaba.com>
> ---
>   arch/riscv/include/asm/stacktrace.h        |  65 ++++++++-
>   arch/riscv/include/asm/stacktrace/common.h | 159 +++++++++++++++++++++
>   2 files changed, 222 insertions(+), 2 deletions(-)
>   create mode 100644 arch/riscv/include/asm/stacktrace/common.h
> 
> diff --git a/arch/riscv/include/asm/stacktrace.h b/arch/riscv/include/asm/stacktrace.h
> index b1495a7e06ce..bc87c4940379 100644
> --- a/arch/riscv/include/asm/stacktrace.h
> +++ b/arch/riscv/include/asm/stacktrace.h
> @@ -3,8 +3,13 @@
>   #ifndef _ASM_RISCV_STACKTRACE_H
>   #define _ASM_RISCV_STACKTRACE_H
>   
> +#include <linux/percpu.h>
>   #include <linux/sched.h>
> +#include <linux/sched/task_stack.h>
> +
> +#include <asm/irq_stack.h>
>   #include <asm/ptrace.h>
> +#include <asm/stacktrace/common.h>
>   
>   struct stackframe {
>   	unsigned long fp;
> @@ -16,14 +21,70 @@ extern void notrace walk_stackframe(struct task_struct *task, struct pt_regs *re
>   extern void dump_backtrace(struct pt_regs *regs, struct task_struct *task,
>   			   const char *loglvl);
>   
> -static inline bool on_thread_stack(void)
> +/*
> + * IRQ stack accessors
> + */
> +static inline struct stack_info stackinfo_get_irq(void)
> +{
> +	unsigned long low = (unsigned long)raw_cpu_read(irq_stack_ptr);

 From https://sashiko.dev/#/patchset/20260629072713.3273743-1-wanghan%40linux.alibaba.com

Will this cause a link error when CONFIG_IRQ_STACKS is disabled?
Looking at arch/riscv/kernel/irq.c, irq_stack_ptr is defined inside an
#ifdef CONFIG_IRQ_STACKS block. Since stackinfo_get_irq() unconditionally
references it here, it seems this might result in an undefined reference
to irq_stack_ptr during linking.

Should this accessor be conditionally compiled, or should it provide a
fallback when CONFIG_IRQ_STACKS is not set?

Thanks.
Shuai

^ permalink raw reply

* Reserving memory on ACPI systems (was: [PATCH v3 04/11] arm64/mm: Add set_memory_device() and set_memory_normal())
From: Mike Rapoport @ 2026-07-08  6:36 UTC (permalink / raw)
  To: Will Deacon
  Cc: Thierry Reding, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Suren Baghdasaryan,
	Michal Hocko, Marek Szyprowski, Robin Murphy, Sumit Semwal,
	Benjamin Gaignard, Brian Starkey, John Stultz, T.J. Mercier,
	Christian König, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Catalin Marinas, Thierry Reding, devicetree,
	linux-tegra, linux-kernel, dri-devel, linux-media,
	linux-arm-kernel, linux-s390, linux-mm, iommu, linaro-mm-sig,
	linux-trace-kernel, Thierry Reding, Chun Ng, Shyam Saini
In-Reply-To: <akftuw9NyRy36fXA@willie-the-truck>

On Fri, Jul 03, 2026 at 06:13:31PM +0100, Will Deacon wrote:
> On Thu, Jul 02, 2026 at 06:41:23PM +0200, Thierry Reding wrote:
> > On Thu, Jul 02, 2026 at 03:46:44PM +0200, Thierry Reding wrote:
> > > On Thu, Jul 02, 2026 at 10:18:47AM +0100, Will Deacon wrote:
> > > > On Wed, Jul 01, 2026 at 06:08:15PM +0200, Thierry Reding wrote:
> > > > > From: Chun Ng <chunn@nvidia.com>
> > > > > 
> > > > > Add helpers to swap PROT_NORMAL and PROT_DEVICE_nGnRnE protection bits
> > > > > on a kernel-linear-map range.
> > > > 
> > > > That sounds like a really terrible idea. Why is this necessary and how
> > > > does it interact with things like load_unaligned_zeropad()?
> > > 
> > > This is necessary because once the memory controller has walled off the
> > > new memory region the CPU must not access it under any circumstances or
> > > it'll cause the CPU to lock up (I think technically it'll hit an SError
> > > but in practice that just means it'll freeze, as far as I can tell).
> > > 
> > > Probably doesn't interact well at all with load_unaligned_zeropad().
> > > 
> > > > I think you should unmap the memory from the linear map and memremap()
> > > > it instead.
> > > 
> > > Given that the memory can never be accessed by the CPU after the memory
> > > controller locks it down, I don't think we'll even need memremap(). The
> > > only thing we really need is the sg_table we hand out via the DMA BUFs
> > > so that they can be used by device drivers to program their DMA engines
> > > internally.
> > > 
> > > Looking through some of the architecture code around this, shouldn't we
> > > simply be using set_memory_encrypted() and set_memory_decrypted() for
> > > this? While they might've been created for slightly other use-cases,
> > > they seem to be doing exactly what we want (i.e. remove the page range
> > > from the linear mapping and flushing it, or restoring the valid bit and
> > > standard permissions, respectively).
> > 
> > Ah... I guess we can't do it because we're not in a realm world and so
> > the early checks in __set_memory_enc_dec() would return early and turn
> > it into a no-op.
> > 
> > How about if I extract a common helper and provide set_memory_p() and
> > set_memory_np() in terms of those. Those are available on x86 and
> > PowerPC as well, so fairly standard. I suppose at that point we're
> > closer to set_memory_valid().
> 
> Why not just call set_direct_map_invalid_noflush() +
> flush_tlb_kernel_range() for each page? We already have APIs for this.
> 
> The big challenge I see with any linear map manipulation, however, is
> that it will rely on can_set_direct_map() which likely means you need to
> give up some performance and/or security to make this work. Does memory
> become inaccesible dynamically at runtime? If not, the best bet would
> be to describe it as a carveout in the DT and mark it as "no-map" so
> we avoid mapping it in the first place.

While I got your attention a bit off-topic but still related question.

AFAIK a lot in arm64 drivers ecosystem relies on that ranges defined as
"/reserved-memory" in DT are linked to devices that use that memory.

EFI/ACPI does not have a similar concept.

Given that more and more systems are using EFI/ACPI rather than DT as their
boot protocol we probably need some way to define such memory carveouts in
the ACPI world.
 
> Will

-- 
Sincerely yours,
Mike.

^ permalink raw reply


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