Linux Trace Kernel
 help / color / mirror / Atom feed
* [PATCH v16 06/20] unwind_user/sframe: Detect .sframe sections in executables
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
	Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
  Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
	Jose E. Marchesi, Beau Belgrave, Florian Weimer,
	Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
	Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
	Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>

From: Josh Poimboeuf <jpoimboe@kernel.org>

When loading an ELF executable, automatically detect an .sframe section
and associate it with the mm_struct.

[ Jens Remus: Fix checkpatch warning "braces {} are not necessary for
single statement blocks". ]

Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---

Notes (jremus):
    Changes in v15:
    - Only add sframe for text that is PT_LOAD in addition to PF_X.
      (Sashiko AI)

 fs/binfmt_elf.c          | 48 +++++++++++++++++++++++++++++++++++++---
 include/uapi/linux/elf.h |  1 +
 2 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
index 16a56b6b3f6c..980a9f229cd1 100644
--- a/fs/binfmt_elf.c
+++ b/fs/binfmt_elf.c
@@ -48,6 +48,7 @@
 #include <linux/uaccess.h>
 #include <uapi/linux/rseq.h>
 #include <linux/rseq.h>
+#include <linux/sframe.h>
 #include <asm/param.h>
 #include <asm/page.h>
 
@@ -637,6 +638,21 @@ static inline int make_prot(u32 p_flags, struct arch_elf_state *arch_state,
 	return arch_elf_adjust_prot(prot, arch_state, has_interp, is_interp);
 }
 
+static void elf_add_sframe(struct elf_phdr *text, struct elf_phdr *sframe,
+			   unsigned long base_addr)
+{
+	unsigned long sframe_start, sframe_end, text_start, text_end;
+
+	sframe_start = base_addr + sframe->p_vaddr;
+	sframe_end   = sframe_start + sframe->p_memsz;
+
+	text_start   = base_addr + text->p_vaddr;
+	text_end     = text_start + text->p_memsz;
+
+	/* Ignore return value, sframe section isn't critical */
+	sframe_add_section(sframe_start, sframe_end, text_start, text_end);
+}
+
 /* This is much more generalized than the library routine read function,
    so we keep this separate.  Technically the library read function
    is only provided so that we can read a.out libraries that have
@@ -647,7 +663,7 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 		unsigned long no_base, struct elf_phdr *interp_elf_phdata,
 		struct arch_elf_state *arch_state)
 {
-	struct elf_phdr *eppnt;
+	struct elf_phdr *eppnt, *sframe_phdr = NULL;
 	unsigned long load_addr = 0;
 	int load_addr_set = 0;
 	unsigned long error = ~0UL;
@@ -673,7 +689,8 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 
 	eppnt = interp_elf_phdata;
 	for (i = 0; i < interp_elf_ex->e_phnum; i++, eppnt++) {
-		if (eppnt->p_type == PT_LOAD) {
+		switch (eppnt->p_type) {
+		case PT_LOAD: {
 			int elf_type = MAP_PRIVATE;
 			int elf_prot = make_prot(eppnt->p_flags, arch_state,
 						 true, true);
@@ -712,6 +729,19 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
 				error = -ENOMEM;
 				goto out;
 			}
+			break;
+		}
+		case PT_GNU_SFRAME:
+			sframe_phdr = eppnt;
+			break;
+		}
+	}
+
+	if (sframe_phdr) {
+		eppnt = interp_elf_phdata;
+		for (i = 0; i < interp_elf_ex->e_phnum; i++, eppnt++) {
+			if (eppnt->p_flags & PF_X && eppnt->p_type == PT_LOAD)
+				elf_add_sframe(eppnt, sframe_phdr, load_addr);
 		}
 	}
 
@@ -836,7 +866,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	int first_pt_load = 1;
 	unsigned long error;
 	struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
-	struct elf_phdr *elf_property_phdata = NULL;
+	struct elf_phdr *elf_property_phdata = NULL, *sframe_phdr = NULL;
 	unsigned long elf_brk;
 	bool brk_moved = false;
 	int retval, i;
@@ -945,6 +975,10 @@ static int load_elf_binary(struct linux_binprm *bprm)
 				executable_stack = EXSTACK_DISABLE_X;
 			break;
 
+		case PT_GNU_SFRAME:
+			sframe_phdr = elf_ppnt;
+			break;
+
 		case PT_LOPROC ... PT_HIPROC:
 			retval = arch_elf_pt_proc(elf_ex, elf_ppnt,
 						  bprm->file, false,
@@ -1242,6 +1276,14 @@ static int load_elf_binary(struct linux_binprm *bprm)
 			elf_brk = k;
 	}
 
+	if (sframe_phdr) {
+		for (i = 0, elf_ppnt = elf_phdata;
+		     i < elf_ex->e_phnum; i++, elf_ppnt++) {
+			if (elf_ppnt->p_flags & PF_X && elf_ppnt->p_type == PT_LOAD)
+				elf_add_sframe(elf_ppnt, sframe_phdr, load_bias);
+		}
+	}
+
 	e_entry = elf_ex->e_entry + load_bias;
 	phdr_addr += load_bias;
 	elf_brk += load_bias;
diff --git a/include/uapi/linux/elf.h b/include/uapi/linux/elf.h
index ee30dcd80901..e2a7dbed2e80 100644
--- a/include/uapi/linux/elf.h
+++ b/include/uapi/linux/elf.h
@@ -41,6 +41,7 @@ typedef __u16	Elf64_Versym;
 #define PT_GNU_STACK	(PT_LOOS + 0x474e551)
 #define PT_GNU_RELRO	(PT_LOOS + 0x474e552)
 #define PT_GNU_PROPERTY	(PT_LOOS + 0x474e553)
+#define PT_GNU_SFRAME	(PT_LOOS + 0x474e554)
 
 
 /* ARM MTE memory tag segment type */
-- 
2.51.0


^ permalink raw reply related

* [PATCH v16 07/20] unwind_user/sframe: Wire up unwind_user to sframe
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
	Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
  Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
	Jose E. Marchesi, Beau Belgrave, Florian Weimer,
	Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
	Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
	Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>

From: Josh Poimboeuf <jpoimboe@kernel.org>

Now that the sframe infrastructure is fully in place, make it work by
hooking it up to the unwind_user interface.

[ Jens Remus: Remove unused pt_regs from unwind_user_next_common() and
its callers.  Simplify unwind_user_next_sframe(). ]

Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
 arch/Kconfig                      |  1 +
 include/linux/unwind_user_types.h |  4 +++-
 kernel/unwind/user.c              | 23 +++++++++++++++++++++++
 3 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/arch/Kconfig b/arch/Kconfig
index 94b2d5e8e529..37549832bd1f 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -488,6 +488,7 @@ config HAVE_UNWIND_USER_FP
 
 config HAVE_UNWIND_USER_SFRAME
 	bool
+	select UNWIND_USER
 
 config HAVE_PERF_REGS
 	bool
diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h
index 412729a269bc..43e4b160883f 100644
--- a/include/linux/unwind_user_types.h
+++ b/include/linux/unwind_user_types.h
@@ -9,7 +9,8 @@
  * available.
  */
 enum unwind_user_type_bits {
-	UNWIND_USER_TYPE_FP_BIT =		0,
+	UNWIND_USER_TYPE_SFRAME_BIT =		0,
+	UNWIND_USER_TYPE_FP_BIT =		1,
 
 	NR_UNWIND_USER_TYPE_BITS,
 };
@@ -17,6 +18,7 @@ enum unwind_user_type_bits {
 enum unwind_user_type {
 	/* Type "none" for the start of stack walk iteration. */
 	UNWIND_USER_TYPE_NONE =			0,
+	UNWIND_USER_TYPE_SFRAME =		BIT(UNWIND_USER_TYPE_SFRAME_BIT),
 	UNWIND_USER_TYPE_FP =			BIT(UNWIND_USER_TYPE_FP_BIT),
 };
 
diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c
index 90ab3c1a205e..1fb272419733 100644
--- a/kernel/unwind/user.c
+++ b/kernel/unwind/user.c
@@ -7,6 +7,7 @@
 #include <linux/sched/task_stack.h>
 #include <linux/unwind_user.h>
 #include <linux/uaccess.h>
+#include <linux/sframe.h>
 
 #define for_each_user_frame(state) \
 	for (unwind_user_start(state); !(state)->done; unwind_user_next(state))
@@ -82,6 +83,16 @@ static int unwind_user_next_fp(struct unwind_user_state *state)
 	return unwind_user_next_common(state, &fp_frame);
 }
 
+static int unwind_user_next_sframe(struct unwind_user_state *state)
+{
+	struct unwind_user_frame frame;
+
+	/* sframe expects the frame to be local storage */
+	if (sframe_find(state->ip, &frame))
+		return -ENOENT;
+	return unwind_user_next_common(state, &frame);
+}
+
 static int unwind_user_next(struct unwind_user_state *state)
 {
 	unsigned long iter_mask = state->available_types;
@@ -95,6 +106,16 @@ static int unwind_user_next(struct unwind_user_state *state)
 
 		state->current_type = type;
 		switch (type) {
+		case UNWIND_USER_TYPE_SFRAME:
+			switch (unwind_user_next_sframe(state)) {
+			case 0:
+				return 0;
+			case -ENOENT:
+				continue;	/* Try next method. */
+			default:
+				state->done = true;
+			}
+			break;
 		case UNWIND_USER_TYPE_FP:
 			if (!unwind_user_next_fp(state))
 				return 0;
@@ -123,6 +144,8 @@ static int unwind_user_start(struct unwind_user_state *state)
 		return -EINVAL;
 	}
 
+	if (current_has_sframe())
+		state->available_types |= UNWIND_USER_TYPE_SFRAME;
 	if (IS_ENABLED(CONFIG_HAVE_UNWIND_USER_FP))
 		state->available_types |= UNWIND_USER_TYPE_FP;
 
-- 
2.51.0


^ permalink raw reply related

* [PATCH v16 01/20] unwind_user: Add generic and arch-specific headers to MAINTAINERS
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
	Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
  Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
	Jose E. Marchesi, Beau Belgrave, Florian Weimer,
	Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
	Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
	Ilya Leoshkevich
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>

Commit 71753c6ed2bf ("unwind_user: Add user space unwinding API with
frame pointer support") introduced include/asm-generic/unwind_user.h
without adding it to MAINTAINERS, as well as any future arch-specific
versions such as the one added by commit 49cf34c0815f
("unwind_user/x86: Enable frame pointer unwinding on x86") which
introduced arch/x86/include/asm/unwind_user.h.

Suggested-by: Dylan Hatch <dylanbhatch@google.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---

Notes (jremus):
    Changes in v14:
    - New patch.

 MAINTAINERS | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index c2c6d79275c6..7434e9d7b33f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27874,6 +27874,8 @@ USERSPACE STACK UNWINDING
 M:	Josh Poimboeuf <jpoimboe@kernel.org>
 M:	Steven Rostedt <rostedt@goodmis.org>
 S:	Maintained
+F:	arch/*/include/asm/unwind_user.h
+F:	include/asm-generic/unwind_user.h
 F:	include/linux/unwind*.h
 F:	kernel/unwind/
 
-- 
2.51.0


^ permalink raw reply related

* Re: [PATCH v6 19/43] KVM: Let userspace disable per-VM mem attributes, enable per-gmem attributes
From: Sean Christopherson @ 2026-05-21 14:21 UTC (permalink / raw)
  To: Fuad Tabba
  Cc: ackerleytng, aik, andrew.jones, binbin.wu, brauner, chao.p.peng,
	david, ira.weiny, jmattson, jthoughton, michael.roth, oupton,
	pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
	steven.price, willy, wyihan, yan.y.zhao, forkloop, pratyush,
	suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, Shuah Khan,
	Vishal Annapurve, Andrew Morton, Chris Li, Kairui Song,
	Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Axel Rasmussen,
	Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng, Shakeel Butt,
	Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka, kvm,
	linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
	linux-mm, linux-coco
In-Reply-To: <CA+EHjTzu=28Sr3=9A9LmJEGz0tBEDbU9taznVV5kdL7s8Nw=Jg@mail.gmail.com>

On Thu, May 21, 2026, Fuad Tabba wrote:
> Hi Ackerley,
> 
> On Thu, 7 May 2026 at 21:22, Ackerley Tng via B4 Relay
> <devnull+ackerleytng.google.com@kernel.org> wrote:
> >
> > From: Sean Christopherson <seanjc@google.com>
> >
> > Make vm_memory_attributes a module parameter so that userspace can disable
> > the use of memory attributes on the VM level.
> >
> > To avoid inconsistencies in the way memory attributes are tracked in KVM
> > and guest_memfd, the vm_memory_attributes module_param is made
> > read-only (0444).
> >
> > Make CONFIG_KVM_VM_MEMORY_ATTRIBUTES selectable, only for (CoCo) VM types
> > that might use vm_memory_attributes.
> >
> > Signed-off-by: Sean Christopherson <seanjc@google.com>
> > Signed-off-by: Ackerley Tng <ackerleytng@google.com>
> 
> Config files always confuse me, but Sashiko might be onto something:
> 
> https://sashiko.dev/#/patchset/20260507-gmem-inplace-conversion-v6-0-91ab5a8b19a4%40google.com?part=19

: Since this prompt does not have a default value, will it default to N
: and silently drop KVM_VM_MEMORY_ATTRIBUTES during configuration updates
: like make olddefconfig?
: 
: Existing userspace VMMs that rely on the KVM_SET_MEMORY_ATTRIBUTES ioctl
: for TDX or SEV VMs might fail to boot if the feature is unexpectedly
: compiled out. Could a default y be used to preserve backwards
: compatibility for existing configurations?

> I think this partially goes back to commit 6, the one I flagged
> yesterday. But also adding "default y" to KVM_VM_MEMORY_ATTRIBUTES?
> The default value should at least fix this issue, but I'm not sure if
> it would cause other problems...

Hrm.  As much as I want per-gmem attributes to be the default going forward,
silently breaking existing setups isn't great.  On the other hand, I'm *very*
skeptical there are any SNP or TDX deployments using a distro kernel, so I'm
still leaning towards forcing the issue and turning per-VM attributes off by
default.

^ permalink raw reply

* [PATCH v2 5/6] rtla/tests: Add unit tests for _parse_args() functions
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
  To: Steven Rostedt, Tomas Glozar
  Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
	Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
	Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
	linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>

Add a test suite for the _parse_args() function of each tool that checks
the params structures (struct common_params, struct osnoise_params,
struct timerlat_params) returned by them for correctness.

One test case is added per option, as well as a few special cases for
tricky combinations of options. Test cases are ordered the same as the
option arrays and help message to allow easy checking of whether all
options are covered.

This should help clarify what the proper command line behavior of RTLA
is in case there are holes in the documentation and verify that the
intended behavior is implemented correctly.

A few necessary changes to the unit tests were done as part of this
commit:

- Unit tests now also link to libsubcmd and its dependencies.
- A new global variable in_unit_test is added to RTLA's CLI interface,
  causing it to skip check for root if running in unit tests. This
  allows the CLI unit tests to run as non-root, like existing unit
  tests.

There is quite a lot of duplication, some of it is mitigated with macros,
but partially it is intentional so that future changes in behavior are
tracked across tools.

Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
 tools/tracing/rtla/src/cli.c                  |   8 +-
 tools/tracing/rtla/src/cli.h                  |   2 +
 tools/tracing/rtla/tests/unit/Build           |   4 +
 tools/tracing/rtla/tests/unit/Makefile.unit   |   2 +-
 .../rtla/tests/unit/cli_params_assert.h       |  68 ++
 .../rtla/tests/unit/osnoise_hist_cli.c        | 557 ++++++++++++++
 .../tracing/rtla/tests/unit/osnoise_top_cli.c | 503 +++++++++++++
 .../rtla/tests/unit/timerlat_hist_cli.c       | 702 ++++++++++++++++++
 .../rtla/tests/unit/timerlat_top_cli.c        | 634 ++++++++++++++++
 tools/tracing/rtla/tests/unit/unit_tests.c    |  11 +
 10 files changed, 2486 insertions(+), 5 deletions(-)
 create mode 100644 tools/tracing/rtla/tests/unit/cli_params_assert.h
 create mode 100644 tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
 create mode 100644 tools/tracing/rtla/tests/unit/osnoise_top_cli.c
 create mode 100644 tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
 create mode 100644 tools/tracing/rtla/tests/unit/timerlat_top_cli.c

diff --git a/tools/tracing/rtla/src/cli.c b/tools/tracing/rtla/src/cli.c
index 7f531519df44..709219341a56 100644
--- a/tools/tracing/rtla/src/cli.c
+++ b/tools/tracing/rtla/src/cli.c
@@ -124,7 +124,7 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv)
 	if (cb_data.trace_output)
 		actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
 
-	if (geteuid())
+	if (geteuid() && !in_unit_test)
 		fatal("osnoise needs root permission");
 
 	return &params->common;
@@ -206,7 +206,7 @@ struct common_params *osnoise_hist_parse_args(int argc, char **argv)
 	if (cb_data.trace_output)
 		actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
 
-	if (geteuid())
+	if (geteuid() && !in_unit_test)
 		fatal("rtla needs root permission");
 
 	if (params->common.hist.no_index && !params->common.hist.with_zeros)
@@ -301,7 +301,7 @@ struct common_params *timerlat_top_parse_args(int argc, char **argv)
 	if (cb_data.trace_output)
 		actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
 
-	if (geteuid())
+	if (geteuid() && !in_unit_test)
 		fatal("rtla needs root permission");
 
 	/*
@@ -427,7 +427,7 @@ struct common_params *timerlat_hist_parse_args(int argc, char **argv)
 	if (cb_data.trace_output)
 		actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
 
-	if (geteuid())
+	if (geteuid() && !in_unit_test)
 		fatal("rtla needs root permission");
 
 	if (params->common.hist.no_irq && params->common.hist.no_thread)
diff --git a/tools/tracing/rtla/src/cli.h b/tools/tracing/rtla/src/cli.h
index c49ccb3e92f5..633a2322cf89 100644
--- a/tools/tracing/rtla/src/cli.h
+++ b/tools/tracing/rtla/src/cli.h
@@ -5,3 +5,5 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv);
 struct common_params *osnoise_hist_parse_args(int argc, char **argv);
 struct common_params *timerlat_top_parse_args(int argc, char **argv);
 struct common_params *timerlat_hist_parse_args(int argc, char **argv);
+
+extern bool in_unit_test;
diff --git a/tools/tracing/rtla/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build
index 2749f4cf202a..16139f17ea1f 100644
--- a/tools/tracing/rtla/tests/unit/Build
+++ b/tools/tracing/rtla/tests/unit/Build
@@ -1,3 +1,7 @@
 unit_tests-y += utils.o
 unit_tests-y += actions.o
 unit_tests-y += unit_tests.o
+unit_tests-y += osnoise_top_cli.o
+unit_tests-y += osnoise_hist_cli.o
+unit_tests-y += timerlat_top_cli.o
+unit_tests-y += timerlat_hist_cli.o
diff --git a/tools/tracing/rtla/tests/unit/Makefile.unit b/tools/tracing/rtla/tests/unit/Makefile.unit
index bacb00164e46..8c33a9583c30 100644
--- a/tools/tracing/rtla/tests/unit/Makefile.unit
+++ b/tools/tracing/rtla/tests/unit/Makefile.unit
@@ -3,7 +3,7 @@
 UNIT_TESTS := $(OUTPUT)unit_tests
 UNIT_TESTS_IN := $(UNIT_TESTS)-in.o
 
-$(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN)
+$(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
 	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(EXTLIBS) -lcheck
 
 $(UNIT_TESTS_IN): fixdep
diff --git a/tools/tracing/rtla/tests/unit/cli_params_assert.h b/tools/tracing/rtla/tests/unit/cli_params_assert.h
new file mode 100644
index 000000000000..4bc7d582fcf4
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/cli_params_assert.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include "../../src/timerlat.h"
+
+/* Tracing Options */
+
+#define CLI_ASSERT_SINGLE_EVENT(_system, _event) do {\
+	ck_assert_ptr_nonnull(params->events);\
+	ck_assert_str_eq(params->events->system, _system);\
+	ck_assert_str_eq(params->events->event, _event);\
+	ck_assert_ptr_null(params->events->next);\
+} while (0)
+
+#define CLI_ASSERT_SINGLE_FILTER(_filter) do {\
+	ck_assert_ptr_nonnull(params->events);\
+	ck_assert_str_eq(params->events->filter, _filter);\
+	ck_assert_ptr_null(params->events->next);\
+} while (0)
+
+#define CLI_ASSERT_SINGLE_TRIGGER(_trigger) do {\
+	ck_assert_ptr_nonnull(params->events);\
+	ck_assert_str_eq(params->events->trigger, _trigger);\
+	ck_assert_ptr_null(params->events->next);\
+} while (0)
+
+/* CPU Configuration */
+
+#define CLI_ASSERT_CPUSET(_field, ...) do {\
+	int n;\
+	int cpus[] = { __VA_ARGS__ };\
+	for (n = 0; n < sizeof(cpus) / sizeof(int); n++)\
+		ck_assert(CPU_ISSET(cpus[n], &params->_field));\
+	ck_assert_int_eq(CPU_COUNT(&params->_field), n);\
+} while (0)
+
+/* Auto Analysis and Actions */
+
+#define CLI_OSNOISE_ASSERT_AUTO(_stop) do {\
+	ck_assert_int_eq(params->stop_us, _stop);\
+	ck_assert_int_eq(osn_params->threshold, 1);\
+	ck_assert_int_eq(params->threshold_actions.len, 1);\
+	ck_assert_int_eq(params->threshold_actions.list[0].type, ACTION_TRACE_OUTPUT);\
+	ck_assert_str_eq(params->threshold_actions.list[0].trace_output, "osnoise_trace.txt");\
+} while (0)
+
+#define CLI_TIMERLAT_ASSERT_AUTO(_threshold) do {\
+	ck_assert_int_eq(params->stop_us, _threshold);\
+	ck_assert_int_eq(params->stop_total_us, _threshold);\
+	ck_assert_int_eq(tlat_params->print_stack, _threshold);\
+	ck_assert_int_eq(params->threshold_actions.len, 1);\
+	ck_assert_int_eq(params->threshold_actions.list[0].type, ACTION_TRACE_OUTPUT);\
+	ck_assert_str_eq(params->threshold_actions.list[0].trace_output, "timerlat_trace.txt");\
+} while (0)
+
+#define CLI_TIMERLAT_ASSERT_AA_ONLY(_threshold) do {\
+	ck_assert_int_eq(params->stop_us, _threshold);\
+	ck_assert_int_eq(params->stop_total_us, _threshold);\
+	ck_assert_int_eq(tlat_params->print_stack, _threshold);\
+	ck_assert_int_eq(params->threshold_actions.len, 0);\
+	ck_assert(params->aa_only);\
+} while (0)
+
+#define CLI_ASSERT_SINGLE_ACTION(_actions, _type, _arg, _valtype, _value) do {\
+	ck_assert_int_eq(params->_actions.len, 1);\
+	ck_assert_int_eq(params->_actions.list[0].type, _type);\
+	ck_assert_##_valtype##_eq(params->_actions.list[0]._arg, _value);\
+} while (0)
diff --git a/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c b/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
new file mode 100644
index 000000000000..3661529f93dc
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
@@ -0,0 +1,557 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL  };\
+			int argc = sizeof(argv) / sizeof(char *) - 1;\
+			struct common_params *params =\
+				osnoise_hist_parse_args(argc, argv);\
+			struct osnoise_params *osn_params __maybe_unused =\
+				to_osnoise_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_period_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-p", "100000");
+
+	ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--period", "100000");
+
+	ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_runtime_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-r", "95000");
+
+	ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_runtime_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--runtime", "95000");
+
+	ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_stop_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-s", "20");
+
+	ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--stop", "20");
+
+	ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-S", "20");
+
+	ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--stop-total", "20");
+
+	ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_threshold_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-T", "5");
+
+	ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_threshold_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--threshold", "5");
+
+	ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+	PARSE_ARGS("osnoise", "hist", "-t");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+	PARSE_ARGS("osnoise", "hist", "-t", "-d", "20");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+	ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+	PARSE_ARGS("osnoise", "hist", "-t", "tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+	PARSE_ARGS("osnoise", "hist", "-t=tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+	PARSE_ARGS("osnoise", "hist", "--trace");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+	PARSE_ARGS("osnoise", "hist", "--trace", "-d", "20");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+	ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+	PARSE_ARGS("osnoise", "hist", "--trace", "tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+	PARSE_ARGS("osnoise", "hist", "--trace=tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-e", "system:event");
+
+	CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--event", "system:event");
+
+	CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+	PARSE_ARGS("osnoise", "hist", "-e", "system:event", "--filter", "filter");
+
+	CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+	PARSE_ARGS("osnoise", "hist", "-e", "system:event", "--trigger", "trigger");
+
+	CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("osnoise", "hist", "-c", "0-1,3");
+
+	ck_assert_str_eq(params->cpus, "0-1,3");
+	CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("osnoise", "hist", "--cpus", "0-1,3");
+
+	ck_assert_str_eq(params->cpus, "0-1,3");
+	CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("osnoise", "hist", "-H", "0-1,3");
+
+	CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("osnoise", "hist", "--house-keeping", "0-1,3");
+
+	CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+	PARSE_ARGS("osnoise", "hist", "-C");
+
+	ck_assert(params->cgroup);
+	ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+	PARSE_ARGS("osnoise", "hist", "-C", "cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+	PARSE_ARGS("osnoise", "hist", "-C=cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+	PARSE_ARGS("osnoise", "hist", "--cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+	PARSE_ARGS("osnoise", "hist", "--cgroup", "cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+	PARSE_ARGS("osnoise", "hist", "--cgroup=cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-P", "f:95");
+
+	ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+	ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--priority", "f:95");
+
+	ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+	ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+/* Histogram Options */
+
+START_TEST(test_bucket_size_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-b", "2");
+
+	ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_bucket_size_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--bucket-size", "2");
+
+	ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_entries_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-E", "512");
+
+	ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_entries_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--entries", "512");
+
+	ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_no_header)
+{
+	PARSE_ARGS("osnoise", "hist", "--no-header");
+
+	ck_assert(params->hist.no_header);
+}
+END_TEST
+
+START_TEST(test_no_index)
+{
+	PARSE_ARGS("osnoise", "hist", "--with-zeros", "--no-index");
+
+	ck_assert(params->hist.no_index);
+}
+END_TEST
+
+START_TEST(test_no_summary)
+{
+	PARSE_ARGS("osnoise", "hist", "--no-summary");
+
+	ck_assert(params->hist.no_summary);
+}
+END_TEST
+
+START_TEST(test_with_zeros)
+{
+	PARSE_ARGS("osnoise", "hist", "--with-zeros");
+
+	ck_assert(params->hist.with_zeros);
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_trace_buffer_size)
+{
+	PARSE_ARGS("osnoise", "hist", "--trace-buffer-size", "200");
+
+	ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+	PARSE_ARGS("osnoise", "hist", "--warm-up", "5");
+
+	ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+	PARSE_ARGS("osnoise", "hist", "-a", "20");
+
+	CLI_OSNOISE_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+	PARSE_ARGS("osnoise", "hist", "--on-end", "trace");
+
+	CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+	PARSE_ARGS("osnoise", "hist", "--on-threshold", "trace");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-D");
+
+	ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--debug");
+
+	ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+	PARSE_ARGS("osnoise", "hist", "-d", "1m");
+
+	ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+	PARSE_ARGS("osnoise", "hist", "--duration", "1m");
+
+	ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *osnoise_hist_cli_suite(void)
+{
+	Suite *s = suite_create("osnoise_hist_cli");
+	TCase *tc;
+
+	tc = tcase_create("tracing_options");
+	tcase_add_test(tc, test_period_short);
+	tcase_add_test(tc, test_period_long);
+	tcase_add_test(tc, test_runtime_short);
+	tcase_add_test(tc, test_runtime_long);
+	tcase_add_test(tc, test_stop_short);
+	tcase_add_test(tc, test_stop_long);
+	tcase_add_test(tc, test_stop_total_short);
+	tcase_add_test(tc, test_stop_total_long);
+	tcase_add_test(tc, test_threshold_short);
+	tcase_add_test(tc, test_threshold_long);
+	tcase_add_test(tc, test_trace_short_noarg);
+	tcase_add_test(tc, test_trace_short_followarg);
+	tcase_add_test(tc, test_trace_short_space);
+	tcase_add_test(tc, test_trace_short_equals);
+	tcase_add_test(tc, test_trace_long_noarg);
+	tcase_add_test(tc, test_trace_long_followarg);
+	tcase_add_test(tc, test_trace_long_space);
+	tcase_add_test(tc, test_trace_long_equals);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("event_configuration");
+	tcase_add_test(tc, test_event_short);
+	tcase_add_test(tc, test_event_long);
+	tcase_add_test(tc, test_filter);
+	tcase_add_test(tc, test_trigger);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("cpu_configuration");
+	tcase_add_test(tc, test_cpus_short);
+	tcase_add_test(tc, test_cpus_long);
+	tcase_add_test(tc, test_housekeeping_short);
+	tcase_add_test(tc, test_housekeeping_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("thread_configuration");
+	tcase_add_test(tc, test_cgroup_short_noarg);
+	tcase_add_test(tc, test_cgroup_short_space);
+	tcase_add_test(tc, test_cgroup_short_equals);
+	tcase_add_test(tc, test_cgroup_long_noarg);
+	tcase_add_test(tc, test_cgroup_long_space);
+	tcase_add_test(tc, test_cgroup_long_equals);
+	tcase_add_test(tc, test_priority_short);
+	tcase_add_test(tc, test_priority_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("histogram_options");
+	tcase_add_test(tc, test_bucket_size_short);
+	tcase_add_test(tc, test_bucket_size_long);
+	tcase_add_test(tc, test_entries_short);
+	tcase_add_test(tc, test_entries_long);
+	tcase_add_test(tc, test_no_header);
+	tcase_add_test(tc, test_no_index);
+	tcase_add_test(tc, test_no_summary);
+	tcase_add_test(tc, test_with_zeros);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("system_tuning");
+	tcase_add_test(tc, test_trace_buffer_size);
+	tcase_add_test(tc, test_warm_up);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("aa_actions");
+	tcase_add_test(tc, test_auto);
+	tcase_add_test(tc, test_on_end);
+	tcase_add_test(tc, test_on_threshold);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("general");
+	tcase_add_test(tc, test_debug_short);
+	tcase_add_test(tc, test_debug_long);
+	tcase_add_test(tc, test_duration_short);
+	tcase_add_test(tc, test_duration_long);
+	suite_add_tcase(s, tc);
+
+	return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/osnoise_top_cli.c b/tools/tracing/rtla/tests/unit/osnoise_top_cli.c
new file mode 100644
index 000000000000..f3a8633cc84e
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/osnoise_top_cli.c
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL  };\
+			int argc = sizeof(argv) / sizeof(char *) - 1;\
+			struct common_params *params =\
+				osnoise_top_parse_args(argc, argv);\
+			struct osnoise_params *osn_params __maybe_unused =\
+				to_osnoise_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_period_short)
+{
+	PARSE_ARGS("osnoise", "top", "-p", "100000");
+
+	ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+	PARSE_ARGS("osnoise", "top", "--period", "100000");
+
+	ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_runtime_short)
+{
+	PARSE_ARGS("osnoise", "top", "-r", "95000");
+
+	ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_runtime_long)
+{
+	PARSE_ARGS("osnoise", "top", "--runtime", "95000");
+
+	ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_stop_short)
+{
+	PARSE_ARGS("osnoise", "top", "-s", "20");
+
+	ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_long)
+{
+	PARSE_ARGS("osnoise", "top", "--stop", "20");
+
+	ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_short)
+{
+	PARSE_ARGS("osnoise", "top", "-S", "20");
+
+	ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_long)
+{
+	PARSE_ARGS("osnoise", "top", "--stop-total", "20");
+
+	ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_threshold_short)
+{
+	PARSE_ARGS("osnoise", "top", "-T", "5");
+
+	ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_threshold_long)
+{
+	PARSE_ARGS("osnoise", "top", "--threshold", "5");
+
+	ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+	PARSE_ARGS("osnoise", "top", "-t");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+	PARSE_ARGS("osnoise", "top", "-t", "-d", "20");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+	ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+	PARSE_ARGS("osnoise", "top", "-t", "tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+	PARSE_ARGS("osnoise", "top", "-t=tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+	PARSE_ARGS("osnoise", "top", "--trace");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+	PARSE_ARGS("osnoise", "top", "--trace", "-d", "20");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+	ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+	PARSE_ARGS("osnoise", "top", "--trace", "tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+	PARSE_ARGS("osnoise", "top", "--trace=tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+	PARSE_ARGS("osnoise", "top", "-e", "system:event");
+
+	CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+	PARSE_ARGS("osnoise", "top", "--event", "system:event");
+
+	CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+	PARSE_ARGS("osnoise", "top", "-e", "system:event", "--filter", "filter");
+
+	CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+	PARSE_ARGS("osnoise", "top", "-e", "system:event", "--trigger", "trigger");
+
+	CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("osnoise", "top", "-c", "0-1,3");
+
+	ck_assert_str_eq(params->cpus, "0-1,3");
+	CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("osnoise", "top", "--cpus", "0-1,3");
+
+	ck_assert_str_eq(params->cpus, "0-1,3");
+	CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("osnoise", "top", "-H", "0-1,3");
+
+	CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("osnoise", "top", "--house-keeping", "0-1,3");
+
+	CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+	PARSE_ARGS("osnoise", "top", "-C");
+
+	ck_assert(params->cgroup);
+	ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+	PARSE_ARGS("osnoise", "top", "-C", "cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+	PARSE_ARGS("osnoise", "top", "-C=cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+	PARSE_ARGS("osnoise", "top", "--cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+	PARSE_ARGS("osnoise", "top", "--cgroup", "cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+	PARSE_ARGS("osnoise", "top", "--cgroup=cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+	PARSE_ARGS("osnoise", "top", "-P", "f:95");
+
+	ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+	ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+	PARSE_ARGS("osnoise", "top", "--priority", "f:95");
+
+	ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+	ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+/* Output */
+
+START_TEST(test_quiet_short)
+{
+	PARSE_ARGS("osnoise", "top", "-q");
+
+	ck_assert(params->quiet);
+}
+END_TEST
+
+START_TEST(test_quiet_long)
+{
+	PARSE_ARGS("osnoise", "top", "--quiet");
+
+	ck_assert(params->quiet);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+	PARSE_ARGS("osnoise", "top", "-a", "20");
+
+	CLI_OSNOISE_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+	PARSE_ARGS("osnoise", "top", "--on-end", "trace");
+
+	CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+	PARSE_ARGS("osnoise", "top", "--on-threshold", "trace");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "osnoise_trace.txt");
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_trace_buffer_size)
+{
+	PARSE_ARGS("osnoise", "top", "--trace-buffer-size", "200");
+
+	ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+	PARSE_ARGS("osnoise", "top", "--warm-up", "5");
+
+	ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+	PARSE_ARGS("osnoise", "top", "-D");
+
+	ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+	PARSE_ARGS("osnoise", "top", "--debug");
+
+	ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+	PARSE_ARGS("osnoise", "top", "-d", "1m");
+
+	ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+	PARSE_ARGS("osnoise", "top", "--duration", "1m");
+
+	ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *osnoise_top_cli_suite(void)
+{
+	Suite *s = suite_create("osnoise_top_cli");
+	TCase *tc;
+
+	tc = tcase_create("tracing_options");
+	tcase_add_test(tc, test_period_short);
+	tcase_add_test(tc, test_period_long);
+	tcase_add_test(tc, test_runtime_short);
+	tcase_add_test(tc, test_runtime_long);
+	tcase_add_test(tc, test_stop_short);
+	tcase_add_test(tc, test_stop_long);
+	tcase_add_test(tc, test_stop_total_short);
+	tcase_add_test(tc, test_stop_total_long);
+	tcase_add_test(tc, test_threshold_short);
+	tcase_add_test(tc, test_threshold_long);
+	tcase_add_test(tc, test_trace_short_noarg);
+	tcase_add_test(tc, test_trace_short_followarg);
+	tcase_add_test(tc, test_trace_short_space);
+	tcase_add_test(tc, test_trace_short_equals);
+	tcase_add_test(tc, test_trace_long_noarg);
+	tcase_add_test(tc, test_trace_long_followarg);
+	tcase_add_test(tc, test_trace_long_space);
+	tcase_add_test(tc, test_trace_long_equals);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("event_configuration");
+	tcase_add_test(tc, test_event_short);
+	tcase_add_test(tc, test_event_long);
+	tcase_add_test(tc, test_filter);
+	tcase_add_test(tc, test_trigger);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("cpu_configuration");
+	tcase_add_test(tc, test_cpus_short);
+	tcase_add_test(tc, test_cpus_long);
+	tcase_add_test(tc, test_housekeeping_short);
+	tcase_add_test(tc, test_housekeeping_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("thread_configuration");
+	tcase_add_test(tc, test_cgroup_short_noarg);
+	tcase_add_test(tc, test_cgroup_short_space);
+	tcase_add_test(tc, test_cgroup_short_equals);
+	tcase_add_test(tc, test_cgroup_long_noarg);
+	tcase_add_test(tc, test_cgroup_long_space);
+	tcase_add_test(tc, test_cgroup_long_equals);
+	tcase_add_test(tc, test_priority_short);
+	tcase_add_test(tc, test_priority_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("output");
+	tcase_add_test(tc, test_quiet_short);
+	tcase_add_test(tc, test_quiet_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("system_tuning");
+	tcase_add_test(tc, test_trace_buffer_size);
+	tcase_add_test(tc, test_warm_up);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("aa_actions");
+	tcase_add_test(tc, test_auto);
+	tcase_add_test(tc, test_on_end);
+	tcase_add_test(tc, test_on_threshold);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("general");
+	tcase_add_test(tc, test_debug_short);
+	tcase_add_test(tc, test_debug_long);
+	tcase_add_test(tc, test_duration_short);
+	tcase_add_test(tc, test_duration_long);
+	suite_add_tcase(s, tc);
+
+	return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c b/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
new file mode 100644
index 000000000000..81dc04596cd1
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
@@ -0,0 +1,702 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include <linux/container_of.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL  };\
+			int argc = sizeof(argv) / sizeof(char *) - 1;\
+			struct common_params *params =\
+				timerlat_hist_parse_args(argc, argv);\
+			struct timerlat_params *tlat_params __maybe_unused =\
+				to_timerlat_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_irq_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-i", "20");
+
+	ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_irq_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--irq", "20");
+
+	ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_period_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-p", "200");
+
+	ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--period", "200");
+
+	ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_stack_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-s", "20");
+
+	ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_stack_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--stack", "20");
+
+	ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_thread_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-T", "20");
+
+	ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_thread_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--thread", "20");
+
+	ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+	PARSE_ARGS("timerlat", "hist", "-t");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+	PARSE_ARGS("timerlat", "hist", "-t", "-d", "20");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+	ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+	PARSE_ARGS("timerlat", "hist", "-t", "tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+	PARSE_ARGS("timerlat", "hist", "-t=tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+	PARSE_ARGS("timerlat", "hist", "--trace");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+	PARSE_ARGS("timerlat", "hist", "--trace", "-d", "20");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+	ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+	PARSE_ARGS("timerlat", "hist", "--trace", "tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+	PARSE_ARGS("timerlat", "hist", "--trace=tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-e", "system:event");
+
+	CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--event", "system:event");
+
+	CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+	PARSE_ARGS("timerlat", "hist", "-e", "system:event", "--filter", "filter");
+
+	CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+	PARSE_ARGS("timerlat", "hist", "-e", "system:event", "--trigger", "trigger");
+
+	CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("timerlat", "hist", "-c", "0-1,3");
+
+	ck_assert_str_eq(params->cpus, "0-1,3");
+	CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("timerlat", "hist", "--cpus", "0-1,3");
+
+	ck_assert_str_eq(params->cpus, "0-1,3");
+	CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("timerlat", "hist", "-H", "0-1,3");
+
+	CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("timerlat", "hist", "--house-keeping", "0-1,3");
+
+	CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+	PARSE_ARGS("timerlat", "hist", "-C");
+
+	ck_assert(params->cgroup);
+	ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+	PARSE_ARGS("timerlat", "hist", "-C", "cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+	PARSE_ARGS("timerlat", "hist", "-C=cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+	PARSE_ARGS("timerlat", "hist", "--cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+	PARSE_ARGS("timerlat", "hist", "--cgroup", "cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+	PARSE_ARGS("timerlat", "hist", "--cgroup=cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_kernel_threads_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-k");
+
+	ck_assert(params->kernel_workload);
+	ck_assert(!params->user_workload);
+	ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_kernel_threads_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--kernel-threads");
+
+	ck_assert(params->kernel_workload);
+	ck_assert(!params->user_workload);
+	ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-P", "f:95");
+
+	ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+	ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--priority", "f:95");
+
+	ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+	ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_user_load_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-U");
+
+	ck_assert(!params->kernel_workload);
+	ck_assert(!params->user_workload);
+	ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_load_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--user-load");
+
+	ck_assert(!params->kernel_workload);
+	ck_assert(!params->user_workload);
+	ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-u");
+
+	ck_assert(!params->kernel_workload);
+	ck_assert(params->user_workload);
+	ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--user-threads");
+
+	ck_assert(!params->kernel_workload);
+	ck_assert(params->user_workload);
+	ck_assert(params->user_data);
+}
+END_TEST
+
+/* Histogram Options */
+
+START_TEST(test_bucket_size_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-b", "2");
+
+	ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_bucket_size_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--bucket-size", "2");
+
+	ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_entries_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-E", "512");
+
+	ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_entries_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--entries", "512");
+
+	ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_no_header)
+{
+	PARSE_ARGS("timerlat", "hist", "--no-header");
+
+	ck_assert(params->hist.no_header);
+}
+END_TEST
+
+START_TEST(test_no_index)
+{
+	PARSE_ARGS("timerlat", "hist", "--with-zeros", "--no-index");
+
+	ck_assert(params->hist.no_index);
+}
+END_TEST
+
+START_TEST(test_no_irq)
+{
+	PARSE_ARGS("timerlat", "hist", "--no-irq");
+
+	ck_assert(params->hist.no_irq);
+}
+END_TEST
+
+START_TEST(test_no_summary)
+{
+	PARSE_ARGS("timerlat", "hist", "--no-summary");
+
+	ck_assert(params->hist.no_summary);
+}
+END_TEST
+
+START_TEST(test_no_thread)
+{
+	PARSE_ARGS("timerlat", "hist", "--no-thread");
+
+	ck_assert(params->hist.no_thread);
+}
+END_TEST
+
+START_TEST(test_with_zeros)
+{
+	PARSE_ARGS("timerlat", "hist", "--with-zeros");
+
+	ck_assert(params->hist.with_zeros);
+}
+END_TEST
+
+/* Output */
+
+START_TEST(test_nano_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-n");
+
+	ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_nano_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--nano");
+
+	ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_deepest_idle_state)
+{
+	PARSE_ARGS("timerlat", "hist", "--deepest-idle-state", "1");
+
+	ck_assert_int_eq(tlat_params->deepest_idle_state, 1);
+}
+END_TEST
+
+START_TEST(test_dma_latency)
+{
+	PARSE_ARGS("timerlat", "hist", "--dma-latency", "10");
+
+	ck_assert_int_eq(tlat_params->dma_latency, 10);
+}
+END_TEST
+
+START_TEST(test_trace_buffer_size)
+{
+	PARSE_ARGS("timerlat", "hist", "--trace-buffer-size", "200");
+
+	ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+	PARSE_ARGS("timerlat", "hist", "--warm-up", "5");
+
+	ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+	PARSE_ARGS("timerlat", "hist", "-a", "20");
+
+	CLI_TIMERLAT_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_bpf_action)
+{
+	PARSE_ARGS("timerlat", "hist", "--bpf-action", "program");
+
+	ck_assert_str_eq(tlat_params->bpf_action_program, "program");
+}
+END_TEST
+
+START_TEST(test_dump_tasks)
+{
+	PARSE_ARGS("timerlat", "hist", "--dump-tasks");
+
+	ck_assert(tlat_params->dump_tasks);
+}
+END_TEST
+
+START_TEST(test_no_aa)
+{
+	PARSE_ARGS("timerlat", "hist", "--no-aa");
+
+	ck_assert(tlat_params->no_aa);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+	PARSE_ARGS("timerlat", "hist", "--on-end", "trace");
+
+	CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+	PARSE_ARGS("timerlat", "hist", "--on-threshold", "trace");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_stack_format)
+{
+	PARSE_ARGS("timerlat", "hist", "--stack-format", "truncate");
+
+	ck_assert_int_eq(tlat_params->stack_format, STACK_FORMAT_TRUNCATE);
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-D");
+
+	ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--debug");
+
+	ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+	PARSE_ARGS("timerlat", "hist", "-d", "1m");
+
+	ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+	PARSE_ARGS("timerlat", "hist", "--duration", "1m");
+
+	ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *timerlat_hist_cli_suite(void)
+{
+	Suite *s = suite_create("timerlat_hist_cli");
+	TCase *tc;
+
+	tc = tcase_create("tracing_options");
+	tcase_add_test(tc, test_irq_short);
+	tcase_add_test(tc, test_irq_long);
+	tcase_add_test(tc, test_period_short);
+	tcase_add_test(tc, test_period_long);
+	tcase_add_test(tc, test_stack_short);
+	tcase_add_test(tc, test_stack_long);
+	tcase_add_test(tc, test_thread_short);
+	tcase_add_test(tc, test_thread_long);
+	tcase_add_test(tc, test_trace_short_noarg);
+	tcase_add_test(tc, test_trace_short_followarg);
+	tcase_add_test(tc, test_trace_short_space);
+	tcase_add_test(tc, test_trace_short_equals);
+	tcase_add_test(tc, test_trace_long_noarg);
+	tcase_add_test(tc, test_trace_long_followarg);
+	tcase_add_test(tc, test_trace_long_space);
+	tcase_add_test(tc, test_trace_long_equals);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("event_configuration");
+	tcase_add_test(tc, test_event_short);
+	tcase_add_test(tc, test_event_long);
+	tcase_add_test(tc, test_filter);
+	tcase_add_test(tc, test_trigger);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("cpu_configuration");
+	tcase_add_test(tc, test_cpus_short);
+	tcase_add_test(tc, test_cpus_long);
+	tcase_add_test(tc, test_housekeeping_short);
+	tcase_add_test(tc, test_housekeeping_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("thread_configuration");
+	tcase_add_test(tc, test_cgroup_short_noarg);
+	tcase_add_test(tc, test_cgroup_short_space);
+	tcase_add_test(tc, test_cgroup_short_equals);
+	tcase_add_test(tc, test_cgroup_long_noarg);
+	tcase_add_test(tc, test_cgroup_long_space);
+	tcase_add_test(tc, test_cgroup_long_equals);
+	tcase_add_test(tc, test_kernel_threads_short);
+	tcase_add_test(tc, test_kernel_threads_long);
+	tcase_add_test(tc, test_priority_short);
+	tcase_add_test(tc, test_priority_long);
+	tcase_add_test(tc, test_user_load_short);
+	tcase_add_test(tc, test_user_load_long);
+	tcase_add_test(tc, test_user_threads_short);
+	tcase_add_test(tc, test_user_threads_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("histogram_options");
+	tcase_add_test(tc, test_bucket_size_short);
+	tcase_add_test(tc, test_bucket_size_long);
+	tcase_add_test(tc, test_entries_short);
+	tcase_add_test(tc, test_entries_long);
+	tcase_add_test(tc, test_no_header);
+	tcase_add_test(tc, test_no_index);
+	tcase_add_test(tc, test_no_irq);
+	tcase_add_test(tc, test_no_summary);
+	tcase_add_test(tc, test_no_thread);
+	tcase_add_test(tc, test_with_zeros);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("output");
+	tcase_add_test(tc, test_nano_short);
+	tcase_add_test(tc, test_nano_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("system_tuning");
+	tcase_add_test(tc, test_deepest_idle_state);
+	tcase_add_test(tc, test_dma_latency);
+	tcase_add_test(tc, test_trace_buffer_size);
+	tcase_add_test(tc, test_warm_up);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("aa_actions");
+	tcase_add_test(tc, test_auto);
+	tcase_add_test(tc, test_bpf_action);
+	tcase_add_test(tc, test_dump_tasks);
+	tcase_add_test(tc, test_no_aa);
+	tcase_add_test(tc, test_on_end);
+	tcase_add_test(tc, test_on_threshold);
+	tcase_add_test(tc, test_stack_format);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("general");
+	tcase_add_test(tc, test_debug_short);
+	tcase_add_test(tc, test_debug_long);
+	tcase_add_test(tc, test_duration_short);
+	tcase_add_test(tc, test_duration_long);
+	suite_add_tcase(s, tc);
+
+	return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/timerlat_top_cli.c b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c
new file mode 100644
index 000000000000..1c39008564c5
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c
@@ -0,0 +1,634 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include <linux/container_of.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL  };\
+			int argc = sizeof(argv) / sizeof(char *) - 1;\
+			struct common_params *params =\
+				timerlat_top_parse_args(argc, argv);\
+			struct timerlat_params *tlat_params __maybe_unused =\
+				to_timerlat_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_irq_short)
+{
+	PARSE_ARGS("timerlat", "top", "-i", "20");
+
+	ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_irq_long)
+{
+	PARSE_ARGS("timerlat", "top", "--irq", "20");
+
+	ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_period_short)
+{
+	PARSE_ARGS("timerlat", "top", "-p", "200");
+
+	ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+	PARSE_ARGS("timerlat", "top", "--period", "200");
+
+	ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_stack_short)
+{
+	PARSE_ARGS("timerlat", "top", "-s", "20");
+
+	ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_stack_long)
+{
+	PARSE_ARGS("timerlat", "top", "--stack", "20");
+
+	ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_thread_short)
+{
+	PARSE_ARGS("timerlat", "top", "-T", "20");
+
+	ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_thread_long)
+{
+	PARSE_ARGS("timerlat", "top", "--thread", "20");
+
+	ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+	PARSE_ARGS("timerlat", "top", "-e", "system:event");
+
+	CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+	PARSE_ARGS("timerlat", "top", "--event", "system:event");
+
+	CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+	PARSE_ARGS("timerlat", "top", "-e", "system:event", "--filter", "filter");
+
+	CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+	PARSE_ARGS("timerlat", "top", "-e", "system:event", "--trigger", "trigger");
+
+	CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+	PARSE_ARGS("timerlat", "top", "-t");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+	PARSE_ARGS("timerlat", "top", "-t", "-d", "20");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+	ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+	PARSE_ARGS("timerlat", "top", "-t", "tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+	PARSE_ARGS("timerlat", "top", "-t=tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+	PARSE_ARGS("timerlat", "top", "--trace");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+	PARSE_ARGS("timerlat", "top", "--trace", "-d", "20");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+	ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+	PARSE_ARGS("timerlat", "top", "--trace", "tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+	PARSE_ARGS("timerlat", "top", "--trace=tracefile");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "tracefile");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("timerlat", "top", "-c", "0-1,3");
+
+	ck_assert_str_eq(params->cpus, "0-1,3");
+	CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("timerlat", "top", "--cpus", "0-1,3");
+
+	ck_assert_str_eq(params->cpus, "0-1,3");
+	CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("timerlat", "top", "-H", "0-1,3");
+
+	CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+	nr_cpus = 4;
+
+	PARSE_ARGS("timerlat", "top", "--house-keeping", "0-1,3");
+
+	CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+	PARSE_ARGS("timerlat", "top", "-C");
+
+	ck_assert(params->cgroup);
+	ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+	PARSE_ARGS("timerlat", "top", "-C", "cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+	PARSE_ARGS("timerlat", "top", "-C=cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+	PARSE_ARGS("timerlat", "top", "--cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+	PARSE_ARGS("timerlat", "top", "--cgroup", "cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+	PARSE_ARGS("timerlat", "top", "--cgroup=cgroup");
+
+	ck_assert(params->cgroup);
+	ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_kernel_threads_short)
+{
+	PARSE_ARGS("timerlat", "top", "-k");
+
+	ck_assert(params->kernel_workload);
+	ck_assert(!params->user_workload);
+	ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_kernel_threads_long)
+{
+	PARSE_ARGS("timerlat", "top", "--kernel-threads");
+
+	ck_assert(params->kernel_workload);
+	ck_assert(!params->user_workload);
+	ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+	PARSE_ARGS("timerlat", "top", "-P", "f:95");
+
+	ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+	ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+	PARSE_ARGS("timerlat", "top", "--priority", "f:95");
+
+	ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+	ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_user_load_short)
+{
+	PARSE_ARGS("timerlat", "top", "-U");
+
+	ck_assert(!params->kernel_workload);
+	ck_assert(!params->user_workload);
+	ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_load_long)
+{
+	PARSE_ARGS("timerlat", "top", "--user-load");
+
+	ck_assert(!params->kernel_workload);
+	ck_assert(!params->user_workload);
+	ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_short)
+{
+	PARSE_ARGS("timerlat", "top", "-u");
+
+	ck_assert(!params->kernel_workload);
+	ck_assert(params->user_workload);
+	ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_long)
+{
+	PARSE_ARGS("timerlat", "top", "--user-threads");
+
+	ck_assert(!params->kernel_workload);
+	ck_assert(params->user_workload);
+	ck_assert(params->user_data);
+}
+END_TEST
+
+/* Output */
+
+START_TEST(test_nano_short)
+{
+	PARSE_ARGS("timerlat", "top", "-n");
+
+	ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_nano_long)
+{
+	PARSE_ARGS("timerlat", "top", "--nano");
+
+	ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_quiet_short)
+{
+	PARSE_ARGS("timerlat", "top", "-q");
+
+	ck_assert(params->quiet);
+}
+END_TEST
+
+START_TEST(test_quiet_long)
+{
+	PARSE_ARGS("timerlat", "top", "--quiet");
+
+	ck_assert(params->quiet);
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_deepest_idle_state)
+{
+	PARSE_ARGS("timerlat", "top", "--deepest-idle-state", "1");
+
+	ck_assert_int_eq(tlat_params->deepest_idle_state, 1);
+}
+END_TEST
+
+START_TEST(test_dma_latency)
+{
+	PARSE_ARGS("timerlat", "top", "--dma-latency", "10");
+
+	ck_assert_int_eq(tlat_params->dma_latency, 10);
+}
+END_TEST
+
+START_TEST(test_trace_buffer_size)
+{
+	PARSE_ARGS("timerlat", "top", "--trace-buffer-size", "200");
+
+	ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+	PARSE_ARGS("timerlat", "top", "--warm-up", "5");
+
+	ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+	PARSE_ARGS("timerlat", "top", "-a", "20");
+
+	CLI_TIMERLAT_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_aa_only)
+{
+	PARSE_ARGS("timerlat", "top", "--aa-only", "20");
+
+	CLI_TIMERLAT_ASSERT_AA_ONLY(20);
+}
+END_TEST
+
+START_TEST(test_bpf_action)
+{
+	PARSE_ARGS("timerlat", "top", "--bpf-action", "program");
+
+	ck_assert_str_eq(tlat_params->bpf_action_program, "program");
+}
+END_TEST
+
+START_TEST(test_dump_tasks)
+{
+	PARSE_ARGS("timerlat", "top", "--dump-tasks");
+
+	ck_assert(tlat_params->dump_tasks);
+}
+END_TEST
+
+START_TEST(test_no_aa)
+{
+	PARSE_ARGS("timerlat", "top", "--no-aa");
+
+	ck_assert(tlat_params->no_aa);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+	PARSE_ARGS("timerlat", "top", "--on-end", "trace");
+
+	CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+	PARSE_ARGS("timerlat", "top", "--on-threshold", "trace");
+
+	CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+				 "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_stack_format)
+{
+	PARSE_ARGS("timerlat", "top", "--stack-format", "truncate");
+
+	ck_assert_int_eq(tlat_params->stack_format, STACK_FORMAT_TRUNCATE);
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+	PARSE_ARGS("timerlat", "top", "-D");
+
+	ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+	PARSE_ARGS("timerlat", "top", "--debug");
+
+	ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+	PARSE_ARGS("timerlat", "top", "-d", "1m");
+
+	ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+	PARSE_ARGS("timerlat", "top", "--duration", "1m");
+
+	ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *timerlat_top_cli_suite(void)
+{
+	Suite *s = suite_create("timerlat_top_cli");
+	TCase *tc;
+
+	tc = tcase_create("tracing_options");
+	tcase_add_test(tc, test_irq_short);
+	tcase_add_test(tc, test_irq_long);
+	tcase_add_test(tc, test_period_short);
+	tcase_add_test(tc, test_period_long);
+	tcase_add_test(tc, test_stack_short);
+	tcase_add_test(tc, test_stack_long);
+	tcase_add_test(tc, test_thread_short);
+	tcase_add_test(tc, test_thread_long);
+	tcase_add_test(tc, test_trace_short_noarg);
+	tcase_add_test(tc, test_trace_short_followarg);
+	tcase_add_test(tc, test_trace_short_space);
+	tcase_add_test(tc, test_trace_short_equals);
+	tcase_add_test(tc, test_trace_long_noarg);
+	tcase_add_test(tc, test_trace_long_followarg);
+	tcase_add_test(tc, test_trace_long_space);
+	tcase_add_test(tc, test_trace_long_equals);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("event_configuration");
+	tcase_add_test(tc, test_event_short);
+	tcase_add_test(tc, test_event_long);
+	tcase_add_test(tc, test_filter);
+	tcase_add_test(tc, test_trigger);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("cpu_configuration");
+	tcase_add_test(tc, test_cpus_short);
+	tcase_add_test(tc, test_cpus_long);
+	tcase_add_test(tc, test_housekeeping_short);
+	tcase_add_test(tc, test_housekeeping_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("thread_configuration");
+	tcase_add_test(tc, test_cgroup_short_noarg);
+	tcase_add_test(tc, test_cgroup_short_space);
+	tcase_add_test(tc, test_cgroup_short_equals);
+	tcase_add_test(tc, test_cgroup_long_noarg);
+	tcase_add_test(tc, test_cgroup_long_space);
+	tcase_add_test(tc, test_cgroup_long_equals);
+	tcase_add_test(tc, test_kernel_threads_short);
+	tcase_add_test(tc, test_kernel_threads_long);
+	tcase_add_test(tc, test_priority_short);
+	tcase_add_test(tc, test_priority_long);
+	tcase_add_test(tc, test_user_load_short);
+	tcase_add_test(tc, test_user_load_long);
+	tcase_add_test(tc, test_user_threads_short);
+	tcase_add_test(tc, test_user_threads_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("output");
+	tcase_add_test(tc, test_nano_short);
+	tcase_add_test(tc, test_nano_long);
+	tcase_add_test(tc, test_quiet_short);
+	tcase_add_test(tc, test_quiet_long);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("system_tuning");
+	tcase_add_test(tc, test_deepest_idle_state);
+	tcase_add_test(tc, test_dma_latency);
+	tcase_add_test(tc, test_trace_buffer_size);
+	tcase_add_test(tc, test_warm_up);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("aa_actions");
+	tcase_add_test(tc, test_auto);
+	tcase_add_test(tc, test_aa_only);
+	tcase_add_test(tc, test_bpf_action);
+	tcase_add_test(tc, test_dump_tasks);
+	tcase_add_test(tc, test_no_aa);
+	tcase_add_test(tc, test_on_end);
+	tcase_add_test(tc, test_on_threshold);
+	tcase_add_test(tc, test_stack_format);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("general");
+	tcase_add_test(tc, test_debug_short);
+	tcase_add_test(tc, test_debug_long);
+	tcase_add_test(tc, test_duration_short);
+	tcase_add_test(tc, test_duration_long);
+	suite_add_tcase(s, tc);
+
+	return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/unit_tests.c b/tools/tracing/rtla/tests/unit/unit_tests.c
index f87d761f9b12..64884f6cbdeb 100644
--- a/tools/tracing/rtla/tests/unit/unit_tests.c
+++ b/tools/tracing/rtla/tests/unit/unit_tests.c
@@ -5,17 +5,28 @@
 #include <stdbool.h>
 
 #include "../../src/utils.h"
+#include "../../src/cli.h"
 
 Suite *utils_suite(void);
 Suite *actions_suite(void);
+Suite *osnoise_top_cli_suite(void);
+Suite *osnoise_hist_cli_suite(void);
+Suite *timerlat_top_cli_suite(void);
+Suite *timerlat_hist_cli_suite(void);
 
 int main(int argc, char *argv[])
 {
 	int num_failed;
 	SRunner *sr;
 
+	in_unit_test = true;
+
 	sr = srunner_create(utils_suite());
 	srunner_add_suite(sr, actions_suite());
+	srunner_add_suite(sr, osnoise_top_cli_suite());
+	srunner_add_suite(sr, osnoise_hist_cli_suite());
+	srunner_add_suite(sr, timerlat_top_cli_suite());
+	srunner_add_suite(sr, timerlat_hist_cli_suite());
 
 	srunner_run_all(sr, CK_VERBOSE);
 	num_failed = srunner_ntests_failed(sr);
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 6/6] rtla/tests: Add unit tests for CLI option callbacks
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
  To: Steven Rostedt, Tomas Glozar
  Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
	Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
	Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
	linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>

In addition to testing all tool_parse_args() functions, test also all
callbacks used for parsing custom option formats.

The callbacks represent a middle layer between the parsing functions
and utility functions dedicated to checking specific argument formats,
for example, scheduling class and duration. Callback tests are run
before parsing functions to make sure any issue in the former is
reported before it is encountered through the latter.

Tests verify both successful parsing and proper rejection of invalid
inputs (via exit tests). To enable testing static callbacks, a pragma
once guard is added to timerlat.h for safe inclusion by cli_p.h.

Add dependency of UNIT_TESTS_IN on LIBSUBCMD_INCLUDES, as the new test
file tests/unit/cli_opt_callback.c includes cli_p.h which includes
subcmd/parse-options.h.

Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
 tools/tracing/rtla/src/timerlat.h             |   2 +
 tools/tracing/rtla/tests/unit/Build           |   1 +
 tools/tracing/rtla/tests/unit/Makefile.unit   |   2 +-
 .../rtla/tests/unit/cli_opt_callback.c        | 704 ++++++++++++++++++
 tools/tracing/rtla/tests/unit/unit_tests.c    |   2 +
 5 files changed, 710 insertions(+), 1 deletion(-)
 create mode 100644 tools/tracing/rtla/tests/unit/cli_opt_callback.c

diff --git a/tools/tracing/rtla/src/timerlat.h b/tools/tracing/rtla/src/timerlat.h
index 37a808f1611e..38ab6b41a15e 100644
--- a/tools/tracing/rtla/src/timerlat.h
+++ b/tools/tracing/rtla/src/timerlat.h
@@ -1,4 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
+#pragma once
+
 #include "osnoise.h"
 
 /*
diff --git a/tools/tracing/rtla/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build
index 16139f17ea1f..d5a0f13922be 100644
--- a/tools/tracing/rtla/tests/unit/Build
+++ b/tools/tracing/rtla/tests/unit/Build
@@ -5,3 +5,4 @@ unit_tests-y += osnoise_top_cli.o
 unit_tests-y += osnoise_hist_cli.o
 unit_tests-y += timerlat_top_cli.o
 unit_tests-y += timerlat_hist_cli.o
+unit_tests-y += cli_opt_callback.o
diff --git a/tools/tracing/rtla/tests/unit/Makefile.unit b/tools/tracing/rtla/tests/unit/Makefile.unit
index 8c33a9583c30..839abda64b76 100644
--- a/tools/tracing/rtla/tests/unit/Makefile.unit
+++ b/tools/tracing/rtla/tests/unit/Makefile.unit
@@ -6,7 +6,7 @@ UNIT_TESTS_IN := $(UNIT_TESTS)-in.o
 $(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
 	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(EXTLIBS) -lcheck
 
-$(UNIT_TESTS_IN): fixdep
+$(UNIT_TESTS_IN): fixdep $(LIBSUBCMD_INCLUDES)
 	make $(build)=unit_tests
 
 unit-tests: FORCE
diff --git a/tools/tracing/rtla/tests/unit/cli_opt_callback.c b/tools/tracing/rtla/tests/unit/cli_opt_callback.c
new file mode 100644
index 000000000000..01647f4227d1
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/cli_opt_callback.c
@@ -0,0 +1,704 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <check.h>
+
+#define RTLA_ALLOW_CLI_P_H
+#include "../../src/cli_p.h"
+#include "cli_params_assert.h"
+
+#define TEST_CALLBACK(value, cb) OPT_CALLBACK('t', "test", value, "test value", "test help", cb)
+
+START_TEST(test_opt_llong_callback_simple)
+{
+	long long test_value = 0;
+	const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+	ck_assert_int_eq(opt_llong_callback(&opt, "1234567890", 0), 0);
+	ck_assert_int_eq(test_value, 1234567890);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_max)
+{
+	long long test_value = 0;
+	const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+	ck_assert_int_eq(opt_llong_callback(&opt, "9223372036854775807", 0), 0);
+	ck_assert_int_eq(test_value, 9223372036854775807LL);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_min)
+{
+	long long test_value = 0;
+	const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+	ck_assert_int_eq(opt_llong_callback(&opt, "-9223372036854775808", 0), 0);
+	ck_assert_int_eq(test_value, ~9223372036854775807LL);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_simple)
+{
+	int test_value = 0;
+	const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+	ck_assert_int_eq(opt_int_callback(&opt, "1234567890", 0), 0);
+	ck_assert_int_eq(test_value, 1234567890);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_max)
+{
+	int test_value = 0;
+	const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+	ck_assert_int_eq(opt_int_callback(&opt, "2147483647", 0), 0);
+	ck_assert_int_eq(test_value, 2147483647);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_min)
+{
+	int test_value = 0;
+	const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+	ck_assert_int_eq(opt_int_callback(&opt, "-2147483648", 0), 0);
+	ck_assert_int_eq(test_value, -2147483648);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_non_numeric)
+{
+	int test_value = 0;
+	const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+	ck_assert_int_eq(opt_int_callback(&opt, "abc", 0), -1);
+	ck_assert_int_eq(test_value, 0);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_non_numeric_suffix)
+{
+	int test_value = 0;
+	const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+	ck_assert_int_eq(opt_int_callback(&opt, "1234567890abc", 0), -1);
+	ck_assert_int_eq(test_value, 0);
+}
+END_TEST
+
+START_TEST(test_opt_cpus_cb)
+{
+	struct common_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_cpus_cb);
+
+	nr_cpus = 4;
+	ck_assert_int_eq(opt_cpus_cb(&opt, "0-3", 0), 0);
+	ck_assert_str_eq(params.cpus, "0-3");
+}
+END_TEST
+
+START_TEST(test_opt_cpus_cb_invalid)
+{
+	struct common_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_cpus_cb);
+
+	nr_cpus = 4;
+	assert(freopen("/dev/null", "w", stderr));
+	opt_cpus_cb(&opt, "0-3,5", 0);
+}
+END_TEST
+
+START_TEST(test_opt_cgroup_cb)
+{
+	struct common_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_cgroup_cb);
+
+	ck_assert_int_eq(opt_cgroup_cb(&opt, "cgroup", 0), 0);
+	ck_assert_int_eq(params.cgroup, 1);
+	ck_assert_str_eq(params.cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_opt_cgroup_cb_equals)
+{
+	struct common_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_cgroup_cb);
+
+	ck_assert_int_eq(opt_cgroup_cb(&opt, "=cgroup", 0), 0);
+	ck_assert_int_eq(params.cgroup, 1);
+	ck_assert_str_eq(params.cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_opt_duration_cb)
+{
+	struct common_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_duration_cb);
+
+	ck_assert_int_eq(opt_duration_cb(&opt, "1m", 0), 0);
+	ck_assert_int_eq(params.duration, 60);
+}
+END_TEST
+
+START_TEST(test_opt_duration_cb_invalid)
+{
+	struct common_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_duration_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_duration_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_event_cb)
+{
+	struct trace_events *events = NULL;
+	const struct option opt = TEST_CALLBACK(&events, opt_event_cb);
+
+	ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_switch", 0), 0);
+	ck_assert_str_eq(events->system, "sched");
+	ck_assert_str_eq(events->event, "sched_switch");
+	ck_assert_ptr_eq(events->next, NULL);
+}
+END_TEST
+
+START_TEST(test_opt_event_cb_multiple)
+{
+	struct trace_events *events = NULL;
+	const struct option opt = TEST_CALLBACK(&events, opt_event_cb);
+
+	ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_switch", 0), 0);
+	ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_wakeup", 0), 0);
+	ck_assert_str_eq(events->system, "sched");
+	ck_assert_str_eq(events->event, "sched_wakeup");
+	ck_assert_str_eq(events->next->system, "sched");
+	ck_assert_str_eq(events->next->event, "sched_switch");
+	ck_assert_ptr_eq(events->next->next, NULL);
+}
+END_TEST
+
+START_TEST(test_opt_housekeeping_cb)
+{
+	struct common_params __params = {0};
+	struct common_params *params = &__params;
+	const struct option opt = TEST_CALLBACK(params, opt_housekeeping_cb);
+
+	nr_cpus = 4;
+	ck_assert_int_eq(opt_housekeeping_cb(&opt, "0-3", 0), 0);
+	ck_assert_int_eq(params->hk_cpus, 1);
+	CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 2, 3);
+}
+END_TEST
+
+START_TEST(test_opt_housekeeping_cb_invalid)
+{
+	struct common_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_housekeeping_cb);
+
+	nr_cpus = 4;
+	assert(freopen("/dev/null", "w", stderr));
+	opt_housekeeping_cb(&opt, "0-3,5", 0);
+}
+END_TEST
+
+START_TEST(test_opt_priority_cb)
+{
+	struct common_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_priority_cb);
+
+	ck_assert_int_eq(opt_priority_cb(&opt, "f:95", 0), 0);
+	ck_assert_int_eq(params.sched_param.sched_policy, SCHED_FIFO);
+	ck_assert_int_eq(params.sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_opt_priority_cb_invalid)
+{
+	struct common_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_priority_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_priority_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_trigger_cb)
+{
+	struct trace_events *events = trace_event_alloc("sched:sched_switch");
+	const struct option opt = TEST_CALLBACK(&events, opt_trigger_cb);
+
+	ck_assert_int_eq(opt_trigger_cb(&opt, "stacktrace", 0), 0);
+	ck_assert_str_eq(events->trigger, "stacktrace");
+}
+END_TEST
+
+START_TEST(test_opt_trigger_cb_no_event)
+{
+	struct trace_events *events = NULL;
+	const struct option opt = TEST_CALLBACK(&events, opt_trigger_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_trigger_cb(&opt, "stacktrace", 0);
+}
+END_TEST
+
+START_TEST(test_opt_filter_cb)
+{
+	struct trace_events *events = trace_event_alloc("sched:sched_switch");
+	const struct option opt = TEST_CALLBACK(&events, opt_filter_cb);
+
+	ck_assert_int_eq(opt_filter_cb(&opt, "comm ~ \"rtla\"", 0), 0);
+	ck_assert_str_eq(events->filter, "comm ~ \"rtla\"");
+}
+END_TEST
+
+START_TEST(test_opt_filter_cb_no_event)
+{
+	struct trace_events *events = NULL;
+	const struct option opt = TEST_CALLBACK(&events, opt_filter_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_filter_cb(&opt, "comm ~ \"rtla\"", 0);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_auto_cb)
+{
+	struct osnoise_params params = {0};
+	struct osnoise_cb_data cb_data = {&params};
+	const struct option opt = TEST_CALLBACK(&cb_data, opt_osnoise_auto_cb);
+
+	ck_assert_int_eq(opt_osnoise_auto_cb(&opt, "10", 0), 0);
+	ck_assert_int_eq(params.common.stop_us, 10);
+	ck_assert_int_eq(params.threshold, 1);
+	ck_assert_str_eq(cb_data.trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_period_cb)
+{
+	unsigned long long period = 0;
+	const struct option opt = TEST_CALLBACK(&period, opt_osnoise_period_cb);
+
+	ck_assert_int_eq(opt_osnoise_period_cb(&opt, "1000000", 0), 0);
+	ck_assert_int_eq(period, 1000000);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_period_cb_invalid)
+{
+	unsigned long long period = 0;
+	const struct option opt = TEST_CALLBACK(&period, opt_osnoise_period_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_osnoise_period_cb(&opt, "10000001", 0);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_runtime_cb)
+{
+	unsigned long long runtime = 0;
+	const struct option opt = TEST_CALLBACK(&runtime, opt_osnoise_runtime_cb);
+
+	ck_assert_int_eq(opt_osnoise_runtime_cb(&opt, "900000", 0), 0);
+	ck_assert_int_eq(runtime, 900000);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_runtime_cb_invalid)
+{
+	unsigned long long runtime = 0;
+	const struct option opt = TEST_CALLBACK(&runtime, opt_osnoise_runtime_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_osnoise_runtime_cb(&opt, "99", 0);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_trace_output_cb)
+{
+	const char *trace_output = NULL;
+	const struct option opt = TEST_CALLBACK(&trace_output, opt_osnoise_trace_output_cb);
+
+	ck_assert_int_eq(opt_osnoise_trace_output_cb(&opt, "trace.txt", 0), 0);
+	ck_assert_str_eq(trace_output, "trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_trace_output_cb_noarg)
+{
+	const char *trace_output = NULL;
+	const struct option opt = TEST_CALLBACK(&trace_output, opt_osnoise_trace_output_cb);
+
+	ck_assert_int_eq(opt_osnoise_trace_output_cb(&opt, NULL, 0), 0);
+	ck_assert_str_eq(trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_threshold_cb)
+{
+	struct actions actions = {0};
+	const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_threshold_cb);
+
+	ck_assert_int_eq(opt_osnoise_on_threshold_cb(&opt, "trace", 0), 0);
+	ck_assert_int_eq(actions.len, 1);
+	ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+	ck_assert_str_eq(actions.list[0].trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_threshold_cb_invalid)
+{
+	struct actions actions = {0};
+	const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_threshold_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_osnoise_on_threshold_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_end_cb)
+{
+	struct actions actions = {0};
+	const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_end_cb);
+
+	ck_assert_int_eq(opt_osnoise_on_end_cb(&opt, "trace", 0), 0);
+	ck_assert_int_eq(actions.len, 1);
+	ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+	ck_assert_str_eq(actions.list[0].trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_end_cb_invalid)
+{
+	struct actions actions = {0};
+	const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_end_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_osnoise_on_end_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_period_cb)
+{
+	long long period = 0;
+	const struct option opt = TEST_CALLBACK(&period, opt_timerlat_period_cb);
+
+	ck_assert_int_eq(opt_timerlat_period_cb(&opt, "1000", 0), 0);
+	ck_assert_int_eq(period, 1000);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_period_cb_invalid)
+{
+	long long period = 0;
+	const struct option opt = TEST_CALLBACK(&period, opt_timerlat_period_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_timerlat_period_cb(&opt, "1000001", 0);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_auto_cb)
+{
+	struct timerlat_params params = {0};
+	struct timerlat_cb_data cb_data = {&params};
+	const struct option opt = TEST_CALLBACK(&cb_data, opt_timerlat_auto_cb);
+
+	ck_assert_int_eq(opt_timerlat_auto_cb(&opt, "10", 0), 0);
+	ck_assert_int_eq(params.common.stop_us, 10);
+	ck_assert_int_eq(params.common.stop_total_us, 10);
+	ck_assert_int_eq(params.print_stack, 10);
+	ck_assert_str_eq(cb_data.trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_dma_latency_cb)
+{
+	int dma_latency = 0;
+	const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
+
+	ck_assert_int_eq(opt_dma_latency_cb(&opt, "1000", 0), 0);
+	ck_assert_int_eq(dma_latency, 1000);
+}
+END_TEST
+
+START_TEST(test_opt_dma_latency_cb_min)
+{
+	int dma_latency = 0;
+	const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_dma_latency_cb(&opt, "-1", 0);
+}
+END_TEST
+
+START_TEST(test_opt_dma_latency_cb_max)
+{
+	int dma_latency = 0;
+	const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_dma_latency_cb(&opt, "10001", 0);
+}
+END_TEST
+
+START_TEST(test_opt_aa_only_cb)
+{
+	struct timerlat_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_aa_only_cb);
+
+	ck_assert_int_eq(opt_aa_only_cb(&opt, "10", 0), 0);
+	ck_assert_int_eq(params.common.stop_us, 10);
+	ck_assert_int_eq(params.common.stop_total_us, 10);
+	ck_assert_int_eq(params.print_stack, 10);
+	ck_assert_int_eq(params.common.aa_only, 1);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_trace_output_cb)
+{
+	const char *trace_output = NULL;
+	const struct option opt = TEST_CALLBACK(&trace_output, opt_timerlat_trace_output_cb);
+
+	ck_assert_int_eq(opt_timerlat_trace_output_cb(&opt, "trace.txt", 0), 0);
+	ck_assert_str_eq(trace_output, "trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_trace_output_cb_noarg)
+{
+	const char *trace_output = NULL;
+	const struct option opt = TEST_CALLBACK(&trace_output, opt_timerlat_trace_output_cb);
+
+	ck_assert_int_eq(opt_timerlat_trace_output_cb(&opt, NULL, 0), 0);
+	ck_assert_str_eq(trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_threshold_cb)
+{
+	struct actions actions = {0};
+	const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_threshold_cb);
+
+	ck_assert_int_eq(opt_timerlat_on_threshold_cb(&opt, "trace", 0), 0);
+	ck_assert_int_eq(actions.len, 1);
+	ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+	ck_assert_str_eq(actions.list[0].trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_threshold_cb_invalid)
+{
+	struct actions actions = {0};
+	const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_threshold_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_timerlat_on_threshold_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_end_cb)
+{
+	struct actions actions = {0};
+	const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_end_cb);
+
+	ck_assert_int_eq(opt_timerlat_on_end_cb(&opt, "trace", 0), 0);
+	ck_assert_int_eq(actions.len, 1);
+	ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+	ck_assert_str_eq(actions.list[0].trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_end_cb_invalid)
+{
+	struct actions actions = {0};
+	const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_end_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_timerlat_on_end_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_user_threads_cb)
+{
+	struct timerlat_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_user_threads_cb);
+
+	ck_assert_int_eq(opt_user_threads_cb(&opt, NULL, 0), 0);
+	ck_assert_int_eq(params.common.user_workload, 1);
+	ck_assert_int_eq(params.common.user_data, 1);
+}
+END_TEST
+
+START_TEST(test_opt_nano_cb)
+{
+	struct timerlat_params params = {0};
+	const struct option opt = TEST_CALLBACK(&params, opt_nano_cb);
+
+	ck_assert_int_eq(opt_nano_cb(&opt, NULL, 0), 0);
+	ck_assert_int_eq(params.common.output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_opt_stack_format_cb)
+{
+	int stack_format = 0;
+	const struct option opt = TEST_CALLBACK(&stack_format, opt_stack_format_cb);
+
+	ck_assert_int_eq(opt_stack_format_cb(&opt, "full", 0), 0);
+	ck_assert_int_eq(stack_format, STACK_FORMAT_FULL);
+}
+END_TEST
+
+START_TEST(test_opt_stack_format_cb_invalid)
+{
+	int stack_format = 0;
+	const struct option opt = TEST_CALLBACK(&stack_format, opt_stack_format_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_stack_format_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_bucket_size_cb)
+{
+	int bucket_size = 0;
+	const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
+
+	ck_assert_int_eq(opt_bucket_size_cb(&opt, "100", 0), 0);
+	ck_assert_int_eq(bucket_size, 100);
+}
+END_TEST
+
+START_TEST(test_opt_bucket_size_min)
+{
+	int bucket_size = 0;
+	const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_bucket_size_cb(&opt, "0", 0);
+}
+END_TEST
+
+START_TEST(test_opt_bucket_size_max)
+{
+	int bucket_size = 0;
+	const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_bucket_size_cb(&opt, "1000001", 0);
+}
+END_TEST
+
+START_TEST(test_opt_entries_cb)
+{
+	int entries = 0;
+	const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
+
+	ck_assert_int_eq(opt_entries_cb(&opt, "100", 0), 0);
+	ck_assert_int_eq(entries, 100);
+}
+END_TEST
+
+START_TEST(test_opt_entries_min)
+{
+	int entries = 0;
+	const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_entries_cb(&opt, "9", 0);
+}
+END_TEST
+
+START_TEST(test_opt_entries_max)
+{
+	int entries = 0;
+	const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
+
+	assert(freopen("/dev/null", "w", stderr));
+	opt_entries_cb(&opt, "10000000", 0);
+}
+END_TEST
+
+Suite *cli_opt_callback_suite(void)
+{
+	Suite *s = suite_create("cli_opt_callback");
+	TCase *tc;
+
+	tc = tcase_create("common");
+	tcase_add_test(tc, test_opt_llong_callback_simple);
+	tcase_add_test(tc, test_opt_llong_callback_max);
+	tcase_add_test(tc, test_opt_llong_callback_min);
+	tcase_add_test(tc, test_opt_int_callback_simple);
+	tcase_add_test(tc, test_opt_int_callback_max);
+	tcase_add_test(tc, test_opt_int_callback_min);
+	tcase_add_test(tc, test_opt_int_callback_non_numeric);
+	tcase_add_test(tc, test_opt_int_callback_non_numeric_suffix);
+	tcase_add_test(tc, test_opt_cpus_cb);
+	tcase_add_exit_test(tc, test_opt_cpus_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_cgroup_cb);
+	tcase_add_test(tc, test_opt_cgroup_cb_equals);
+	tcase_add_test(tc, test_opt_duration_cb);
+	tcase_add_exit_test(tc, test_opt_duration_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_event_cb);
+	tcase_add_test(tc, test_opt_event_cb_multiple);
+	tcase_add_test(tc, test_opt_housekeeping_cb);
+	tcase_add_exit_test(tc, test_opt_housekeeping_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_priority_cb);
+	tcase_add_exit_test(tc, test_opt_priority_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_trigger_cb);
+	tcase_add_exit_test(tc, test_opt_trigger_cb_no_event, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_filter_cb);
+	tcase_add_exit_test(tc, test_opt_filter_cb_no_event, EXIT_FAILURE);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("osnoise");
+	tcase_add_test(tc, test_opt_osnoise_auto_cb);
+	tcase_add_test(tc, test_opt_osnoise_period_cb);
+	tcase_add_exit_test(tc, test_opt_osnoise_period_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_osnoise_runtime_cb);
+	tcase_add_exit_test(tc, test_opt_osnoise_runtime_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_osnoise_trace_output_cb);
+	tcase_add_test(tc, test_opt_osnoise_trace_output_cb_noarg);
+	tcase_add_test(tc, test_opt_osnoise_on_threshold_cb);
+	tcase_add_exit_test(tc, test_opt_osnoise_on_threshold_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_osnoise_on_end_cb);
+	tcase_add_exit_test(tc, test_opt_osnoise_on_end_cb_invalid, EXIT_FAILURE);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("timerlat");
+	tcase_add_test(tc, test_opt_timerlat_period_cb);
+	tcase_add_exit_test(tc, test_opt_timerlat_period_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_timerlat_auto_cb);
+	tcase_add_test(tc, test_opt_dma_latency_cb);
+	tcase_add_exit_test(tc, test_opt_dma_latency_cb_min, EXIT_FAILURE);
+	tcase_add_exit_test(tc, test_opt_dma_latency_cb_max, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_aa_only_cb);
+	tcase_add_test(tc, test_opt_timerlat_trace_output_cb);
+	tcase_add_test(tc, test_opt_timerlat_trace_output_cb_noarg);
+	tcase_add_test(tc, test_opt_timerlat_on_threshold_cb);
+	tcase_add_exit_test(tc, test_opt_timerlat_on_threshold_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_timerlat_on_end_cb);
+	tcase_add_exit_test(tc, test_opt_timerlat_on_end_cb_invalid, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_user_threads_cb);
+	tcase_add_test(tc, test_opt_nano_cb);
+	tcase_add_test(tc, test_opt_stack_format_cb);
+	tcase_add_exit_test(tc, test_opt_stack_format_cb_invalid, EXIT_FAILURE);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("histogram");
+	tcase_add_test(tc, test_opt_bucket_size_cb);
+	tcase_add_exit_test(tc, test_opt_bucket_size_min, EXIT_FAILURE);
+	tcase_add_exit_test(tc, test_opt_bucket_size_max, EXIT_FAILURE);
+	tcase_add_test(tc, test_opt_entries_cb);
+	tcase_add_exit_test(tc, test_opt_entries_min, EXIT_FAILURE);
+	tcase_add_exit_test(tc, test_opt_entries_max, EXIT_FAILURE);
+	suite_add_tcase(s, tc);
+
+	return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/unit_tests.c b/tools/tracing/rtla/tests/unit/unit_tests.c
index 64884f6cbdeb..75ca813f81ca 100644
--- a/tools/tracing/rtla/tests/unit/unit_tests.c
+++ b/tools/tracing/rtla/tests/unit/unit_tests.c
@@ -13,6 +13,7 @@ Suite *osnoise_top_cli_suite(void);
 Suite *osnoise_hist_cli_suite(void);
 Suite *timerlat_top_cli_suite(void);
 Suite *timerlat_hist_cli_suite(void);
+Suite *cli_opt_callback_suite(void);
 
 int main(int argc, char *argv[])
 {
@@ -22,6 +23,7 @@ int main(int argc, char *argv[])
 	in_unit_test = true;
 
 	sr = srunner_create(utils_suite());
+	srunner_add_suite(sr, cli_opt_callback_suite());
 	srunner_add_suite(sr, actions_suite());
 	srunner_add_suite(sr, osnoise_top_cli_suite());
 	srunner_add_suite(sr, osnoise_hist_cli_suite());
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 4/6] rtla: Parse cmdline using libsubcmd
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
  To: Steven Rostedt, Tomas Glozar
  Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
	Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
	Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
	linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>

Instead of using getopt_long() directly to parse the command line
arguments given to an RTLA tool, use libsubcmd's parse_options().

Utilizing libsubcmd for parsing command line arguments has several
benefits:

- A help message is automatically generated by libsubcmd from the
  specification, removing the need of writing it by hand.
- Options are sorted into groups based on which part of tracing (CPU,
  thread, auto-analysis, tuning, histogram) they relate to.
- Common parsing patterns for numerical and boolean values now share
  code, with the target variable being stored in the option array.

To avoid duplication of the option parsing logic, RTLA-specific
macros defining struct option values are created:

- RTLA_OPT_* for options common to all tools
- OSNOISE_OPT_* and TIMERLAT_OPT_* for options specific to
  osnoise/timerlat tools
- HIST_OPT_* macros for options specific to histogram-based tools.

Individual *_parse_args() functions then construct an array out of
these macros that is then passed to libsubcmd's parse_options().

All code specific to command line options parsing is moved out of the
individual tool files into a new file, cli.c, which also contains the
contents of the rtla.c file. A private header, cli_p.h, is added
alongside the public header cli.h, so that unit tests are able to test
statically declared option callbacks.

Minor changes:

- The return value of tool-level help option changes to 129, as this is
  the value set by libsubcmd; this is reflected in affected test cases.
  The implementation of help for command-level and tracer-level help
  is set to 129 as well for consistency, and the change is reflected in
  exit value documentation.
- Related to the above, {rtla,osnoise,timerlat}_usage() are marked
  __noreturn and exit() is removed from after they are called for
  cleaner code.
- The error messages for invalid argument for options --dma-latency and
  -E/--entries were corrected, fixing off-by-one in the limits.

Note that unsetting options (using --no-<opt> syntax) is currently not
implemented for options that use custom callbacks. For --irq and
--thread, it will never be implemented, as they conflict with already
existing --no-irq and --no-thread with a different meaning.

Assisted-by: Composer:composer-1.5
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
 Documentation/tools/rtla/common_appendix.txt |   7 +-
 tools/tracing/rtla/src/Build                 |   2 +-
 tools/tracing/rtla/src/cli.c                 | 537 +++++++++++++++
 tools/tracing/rtla/src/cli.h                 |   7 +
 tools/tracing/rtla/src/cli_p.h               | 670 +++++++++++++++++++
 tools/tracing/rtla/src/common.c              | 109 ---
 tools/tracing/rtla/src/common.h              |  27 +-
 tools/tracing/rtla/src/osnoise.c             |   9 +-
 tools/tracing/rtla/src/osnoise_hist.c        | 221 +-----
 tools/tracing/rtla/src/osnoise_top.c         | 200 +-----
 tools/tracing/rtla/src/rtla.c                |  92 ---
 tools/tracing/rtla/src/timerlat.c            |   9 +-
 tools/tracing/rtla/src/timerlat.h            |   4 +-
 tools/tracing/rtla/src/timerlat_hist.c       | 317 +--------
 tools/tracing/rtla/src/timerlat_top.c        | 286 +-------
 tools/tracing/rtla/src/utils.c               |  28 +-
 tools/tracing/rtla/src/utils.h               |   3 +-
 tools/tracing/rtla/tests/hwnoise.t           |   2 +-
 tools/tracing/rtla/tests/osnoise.t           |   6 +-
 tools/tracing/rtla/tests/timerlat.t          |   6 +-
 20 files changed, 1256 insertions(+), 1286 deletions(-)
 create mode 100644 tools/tracing/rtla/src/cli.c
 create mode 100644 tools/tracing/rtla/src/cli.h
 create mode 100644 tools/tracing/rtla/src/cli_p.h
 delete mode 100644 tools/tracing/rtla/src/rtla.c

diff --git a/Documentation/tools/rtla/common_appendix.txt b/Documentation/tools/rtla/common_appendix.txt
index 8c90a02588e7..68cb15840d3a 100644
--- a/Documentation/tools/rtla/common_appendix.txt
+++ b/Documentation/tools/rtla/common_appendix.txt
@@ -26,9 +26,10 @@ EXIT STATUS
 
 ::
 
- 0  Passed: the test did not hit the stop tracing condition
- 1  Error: invalid argument
- 2  Failed: the test hit the stop tracing condition
+ 0    Passed: the test did not hit the stop tracing condition
+ 1    Error: invalid argument
+ 2    Failed: the test hit the stop tracing condition
+ 129  Help: either user requested help or incorrect option was specified
 
 REPORTING BUGS
 ==============
diff --git a/tools/tracing/rtla/src/Build b/tools/tracing/rtla/src/Build
index 329e24a40cf7..a1f3ab927207 100644
--- a/tools/tracing/rtla/src/Build
+++ b/tools/tracing/rtla/src/Build
@@ -11,4 +11,4 @@ rtla-y += timerlat_hist.o
 rtla-y += timerlat_u.o
 rtla-y += timerlat_aa.o
 rtla-y += timerlat_bpf.o
-rtla-y += rtla.o
+rtla-y += cli.o
diff --git a/tools/tracing/rtla/src/cli.c b/tools/tracing/rtla/src/cli.c
new file mode 100644
index 000000000000..7f531519df44
--- /dev/null
+++ b/tools/tracing/rtla/src/cli.c
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <linux/compiler.h>
+
+#define RTLA_ALLOW_CLI_P_H
+#include "cli_p.h"
+
+static const char * const osnoise_top_usage[] = {
+	"rtla osnoise [top] [<options>] [-h|--help]",
+	NULL,
+};
+
+static const char * const osnoise_hist_usage[] = {
+	"rtla osnoise hist [<options>] [-h|--help]",
+	NULL,
+};
+
+static const char * const timerlat_top_usage[] = {
+	"rtla timerlat [top] [<options>] [-h|--help]",
+	NULL,
+};
+
+static const char * const timerlat_hist_usage[] = {
+	"rtla timerlat hist [<options>] [-h|--help]",
+	NULL,
+};
+
+static const char * const hwnoise_usage[] = {
+	"rtla hwnoise [<options>] [-h|--help]",
+	NULL,
+};
+
+static const int common_parse_options_flags = PARSE_OPT_OPTARG_ALLOW_NEXT;
+
+bool in_unit_test;
+
+/*
+ * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
+ */
+struct common_params *osnoise_top_parse_args(int argc, char **argv)
+{
+	struct osnoise_params *params;
+	struct osnoise_cb_data cb_data;
+	const char * const *usage;
+
+	params = calloc_fatal(1, sizeof(*params));
+
+	cb_data.params = params;
+	cb_data.trace_output = NULL;
+
+	if (strcmp(argv[0], "hwnoise") == 0) {
+		params->mode = MODE_HWNOISE;
+		/*
+		 * Reduce CPU usage for 75% to avoid killing the system.
+		 */
+		params->runtime = 750000;
+		params->period = 1000000;
+		usage = hwnoise_usage;
+	} else {
+		usage = osnoise_top_usage;
+	}
+
+	const struct option osnoise_top_options[] = {
+	OPT_GROUP("Tracing Options:"),
+		OSNOISE_OPT_PERIOD,
+		OSNOISE_OPT_RUNTIME,
+		RTLA_OPT_STOP('s', "stop", "single sample"),
+		RTLA_OPT_STOP_TOTAL('S', "stop-total", "total sample"),
+		OSNOISE_OPT_THRESHOLD,
+		RTLA_OPT_TRACE_OUTPUT("osnoise", opt_osnoise_trace_output_cb),
+
+	OPT_GROUP("Event Configuration:"),
+		RTLA_OPT_EVENT,
+		RTLA_OPT_FILTER,
+		RTLA_OPT_TRIGGER,
+
+	OPT_GROUP("CPU Configuration:"),
+		RTLA_OPT_CPUS,
+		RTLA_OPT_HOUSEKEEPING,
+
+	OPT_GROUP("Thread Configuration:"),
+		RTLA_OPT_PRIORITY,
+		RTLA_OPT_CGROUP,
+
+	OPT_GROUP("Output:"),
+		RTLA_OPT_QUIET,
+
+	OPT_GROUP("System Tuning:"),
+		RTLA_OPT_TRACE_BUFFER_SIZE,
+		RTLA_OPT_WARM_UP,
+
+	OPT_GROUP("Auto Analysis and Actions:"),
+		RTLA_OPT_AUTO(opt_osnoise_auto_cb),
+		RTLA_OPT_ON_THRESHOLD("stop-total", opt_osnoise_on_threshold_cb),
+		RTLA_OPT_ON_END(opt_osnoise_on_end_cb),
+
+	OPT_GROUP("General:"),
+		RTLA_OPT_DURATION,
+		RTLA_OPT_DEBUG,
+
+	OPT_END(),
+	};
+
+	actions_init(&params->common.threshold_actions);
+	actions_init(&params->common.end_actions);
+
+	argc = parse_options(argc, (const char **)argv,
+			     osnoise_top_options,
+			     usage,
+			     common_parse_options_flags);
+	if (argc < 0)
+		return NULL;
+
+	if (cb_data.trace_output)
+		actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
+
+	if (geteuid())
+		fatal("osnoise needs root permission");
+
+	return &params->common;
+}
+
+/*
+ * osnoise_hist_parse_args - allocs, parse and fill the cmd line parameters
+ */
+struct common_params *osnoise_hist_parse_args(int argc, char **argv)
+{
+	struct osnoise_params *params;
+	struct osnoise_cb_data cb_data;
+
+	params = calloc_fatal(1, sizeof(*params));
+
+	cb_data.params = params;
+	cb_data.trace_output = NULL;
+
+	const struct option osnoise_hist_options[] = {
+	OPT_GROUP("Tracing Options:"),
+		OSNOISE_OPT_PERIOD,
+		OSNOISE_OPT_RUNTIME,
+		RTLA_OPT_STOP('s', "stop", "single sample"),
+		RTLA_OPT_STOP_TOTAL('S', "stop-total", "total sample"),
+		OSNOISE_OPT_THRESHOLD,
+		RTLA_OPT_TRACE_OUTPUT("osnoise", opt_osnoise_trace_output_cb),
+
+	OPT_GROUP("Event Configuration:"),
+		RTLA_OPT_EVENT,
+		RTLA_OPT_FILTER,
+		RTLA_OPT_TRIGGER,
+
+	OPT_GROUP("CPU Configuration:"),
+		RTLA_OPT_CPUS,
+		RTLA_OPT_HOUSEKEEPING,
+
+	OPT_GROUP("Thread Configuration:"),
+		RTLA_OPT_PRIORITY,
+		RTLA_OPT_CGROUP,
+
+	OPT_GROUP("Histogram Options:"),
+		HIST_OPT_BUCKET_SIZE,
+		HIST_OPT_ENTRIES,
+		HIST_OPT_NO_HEADER,
+		HIST_OPT_NO_SUMMARY,
+		HIST_OPT_NO_INDEX,
+		HIST_OPT_WITH_ZEROS,
+
+	OPT_GROUP("System Tuning:"),
+		RTLA_OPT_TRACE_BUFFER_SIZE,
+		RTLA_OPT_WARM_UP,
+
+	OPT_GROUP("Auto Analysis and Actions:"),
+		RTLA_OPT_AUTO(opt_osnoise_auto_cb),
+		RTLA_OPT_ON_THRESHOLD("stop-total", opt_osnoise_on_threshold_cb),
+		RTLA_OPT_ON_END(opt_osnoise_on_end_cb),
+
+	OPT_GROUP("General:"),
+		RTLA_OPT_DURATION,
+		RTLA_OPT_DEBUG,
+
+	OPT_END(),
+	};
+
+	actions_init(&params->common.threshold_actions);
+	actions_init(&params->common.end_actions);
+
+	/* display data in microseconds */
+	params->common.output_divisor = 1000;
+	params->common.hist.bucket_size = 1;
+	params->common.hist.entries = 256;
+
+	argc = parse_options(argc, (const char **)argv,
+			     osnoise_hist_options, osnoise_hist_usage,
+			     common_parse_options_flags);
+	if (argc < 0)
+		return NULL;
+
+	if (cb_data.trace_output)
+		actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
+
+	if (geteuid())
+		fatal("rtla needs root permission");
+
+	if (params->common.hist.no_index && !params->common.hist.with_zeros)
+		fatal("no-index set and with-zeros not set - it does not make sense");
+
+	return &params->common;
+}
+
+struct common_params *timerlat_top_parse_args(int argc, char **argv)
+{
+	struct timerlat_params *params;
+	struct timerlat_cb_data cb_data;
+
+	params = calloc_fatal(1, sizeof(*params));
+
+	cb_data.params = params;
+	cb_data.trace_output = NULL;
+
+	const struct option timerlat_top_options[] = {
+	OPT_GROUP("Tracing Options:"),
+		TIMERLAT_OPT_PERIOD,
+		RTLA_OPT_STOP('i', "irq", "irq latency"),
+		RTLA_OPT_STOP_TOTAL('T', "thread", "thread latency"),
+		TIMERLAT_OPT_STACK,
+		RTLA_OPT_TRACE_OUTPUT("timerlat", opt_timerlat_trace_output_cb),
+
+	OPT_GROUP("Event Configuration:"),
+		RTLA_OPT_EVENT,
+		RTLA_OPT_FILTER,
+		RTLA_OPT_TRIGGER,
+
+	OPT_GROUP("CPU Configuration:"),
+		RTLA_OPT_CPUS,
+		RTLA_OPT_HOUSEKEEPING,
+
+	OPT_GROUP("Thread Configuration:"),
+		RTLA_OPT_PRIORITY,
+		RTLA_OPT_CGROUP,
+		RTLA_OPT_USER_THREADS,
+		RTLA_OPT_KERNEL_THREADS,
+		RTLA_OPT_USER_LOAD,
+
+	OPT_GROUP("Output:"),
+		TIMERLAT_OPT_NANO,
+		RTLA_OPT_QUIET,
+
+	OPT_GROUP("System Tuning:"),
+		TIMERLAT_OPT_DMA_LATENCY,
+		TIMERLAT_OPT_DEEPEST_IDLE_STATE,
+		RTLA_OPT_TRACE_BUFFER_SIZE,
+		RTLA_OPT_WARM_UP,
+
+	OPT_GROUP("Auto Analysis and Actions:"),
+		RTLA_OPT_AUTO(opt_timerlat_auto_cb),
+		TIMERLAT_OPT_AA_ONLY,
+		TIMERLAT_OPT_NO_AA,
+		TIMERLAT_OPT_DUMPS_TASKS,
+		RTLA_OPT_ON_THRESHOLD("latency", opt_timerlat_on_threshold_cb),
+		RTLA_OPT_ON_END(opt_timerlat_on_end_cb),
+		TIMERLAT_OPT_BPF_ACTION,
+		TIMERLAT_OPT_STACK_FORMAT,
+
+	OPT_GROUP("General:"),
+		RTLA_OPT_DURATION,
+		RTLA_OPT_DEBUG,
+
+	OPT_END(),
+	};
+
+	actions_init(&params->common.threshold_actions);
+	actions_init(&params->common.end_actions);
+
+	/* disabled by default */
+	params->dma_latency = -1;
+	params->deepest_idle_state = -2;
+
+	/* display data in microseconds */
+	params->common.output_divisor = 1000;
+
+	/* default to BPF mode */
+	params->mode = TRACING_MODE_BPF;
+
+	/* default to truncate stack format */
+	params->stack_format = STACK_FORMAT_TRUNCATE;
+
+	argc = parse_options(argc, (const char **)argv,
+			     timerlat_top_options, timerlat_top_usage,
+			     common_parse_options_flags);
+	if (argc < 0)
+		return NULL;
+
+	if (cb_data.trace_output)
+		actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
+
+	if (geteuid())
+		fatal("rtla needs root permission");
+
+	/*
+	 * Auto analysis only happens if stop tracing, thus:
+	 */
+	if (!params->common.stop_us && !params->common.stop_total_us)
+		params->no_aa = 1;
+
+	if (params->no_aa && params->common.aa_only)
+		fatal("--no-aa and --aa-only are mutually exclusive!");
+
+	if (params->common.kernel_workload && params->common.user_workload)
+		fatal("--kernel-threads and --user-threads are mutually exclusive!");
+
+	/*
+	 * If auto-analysis or trace output is enabled, switch from BPF mode to
+	 * mixed mode
+	 */
+	if (params->mode == TRACING_MODE_BPF &&
+		(params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] ||
+		params->common.end_actions.present[ACTION_TRACE_OUTPUT] ||
+		!params->no_aa))
+		params->mode = TRACING_MODE_MIXED;
+
+	return &params->common;
+}
+
+struct common_params *timerlat_hist_parse_args(int argc, char **argv)
+{
+	struct timerlat_params *params;
+	struct timerlat_cb_data cb_data;
+
+	params = calloc_fatal(1, sizeof(*params));
+
+	cb_data.params = params;
+	cb_data.trace_output = NULL;
+
+	const struct option timerlat_hist_options[] = {
+	OPT_GROUP("Tracing Options:"),
+		TIMERLAT_OPT_PERIOD,
+		RTLA_OPT_STOP('i', "irq", "irq latency"),
+		RTLA_OPT_STOP_TOTAL('T', "thread", "thread latency"),
+		TIMERLAT_OPT_STACK,
+		RTLA_OPT_TRACE_OUTPUT("timerlat", opt_timerlat_trace_output_cb),
+
+	OPT_GROUP("Event Configuration:"),
+		RTLA_OPT_EVENT,
+		RTLA_OPT_FILTER,
+		RTLA_OPT_TRIGGER,
+
+	OPT_GROUP("CPU Configuration:"),
+		RTLA_OPT_CPUS,
+		RTLA_OPT_HOUSEKEEPING,
+
+	OPT_GROUP("Thread Configuration:"),
+		RTLA_OPT_PRIORITY,
+		RTLA_OPT_CGROUP,
+		RTLA_OPT_USER_THREADS,
+		RTLA_OPT_KERNEL_THREADS,
+		RTLA_OPT_USER_LOAD,
+
+	OPT_GROUP("Histogram Options:"),
+		HIST_OPT_BUCKET_SIZE,
+		HIST_OPT_ENTRIES,
+		HIST_OPT_NO_IRQ,
+		HIST_OPT_NO_THREAD,
+		HIST_OPT_NO_HEADER,
+		HIST_OPT_NO_SUMMARY,
+		HIST_OPT_NO_INDEX,
+		HIST_OPT_WITH_ZEROS,
+
+	OPT_GROUP("Output:"),
+		TIMERLAT_OPT_NANO,
+
+	OPT_GROUP("System Tuning:"),
+		TIMERLAT_OPT_DMA_LATENCY,
+		TIMERLAT_OPT_DEEPEST_IDLE_STATE,
+		RTLA_OPT_TRACE_BUFFER_SIZE,
+		RTLA_OPT_WARM_UP,
+
+	OPT_GROUP("Auto Analysis and Actions:"),
+		RTLA_OPT_AUTO(opt_timerlat_auto_cb),
+		TIMERLAT_OPT_NO_AA,
+		TIMERLAT_OPT_DUMPS_TASKS,
+		RTLA_OPT_ON_THRESHOLD("latency", opt_timerlat_on_threshold_cb),
+		RTLA_OPT_ON_END(opt_timerlat_on_end_cb),
+		TIMERLAT_OPT_BPF_ACTION,
+		TIMERLAT_OPT_STACK_FORMAT,
+
+	OPT_GROUP("General:"),
+		RTLA_OPT_DURATION,
+		RTLA_OPT_DEBUG,
+
+	OPT_END(),
+	};
+
+	actions_init(&params->common.threshold_actions);
+	actions_init(&params->common.end_actions);
+
+	/* disabled by default */
+	params->dma_latency = -1;
+
+	/* disabled by default */
+	params->deepest_idle_state = -2;
+
+	/* display data in microseconds */
+	params->common.output_divisor = 1000;
+	params->common.hist.bucket_size = 1;
+	params->common.hist.entries = 256;
+
+	/* default to BPF mode */
+	params->mode = TRACING_MODE_BPF;
+
+	/* default to truncate stack format */
+	params->stack_format = STACK_FORMAT_TRUNCATE;
+
+	argc = parse_options(argc, (const char **)argv,
+			     timerlat_hist_options, timerlat_hist_usage,
+			     common_parse_options_flags);
+	if (argc < 0)
+		return NULL;
+
+	if (cb_data.trace_output)
+		actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
+
+	if (geteuid())
+		fatal("rtla needs root permission");
+
+	if (params->common.hist.no_irq && params->common.hist.no_thread)
+		fatal("no-irq and no-thread set, there is nothing to do here");
+
+	if (params->common.hist.no_index && !params->common.hist.with_zeros)
+		fatal("no-index set with with-zeros is not set - it does not make sense");
+
+	/*
+	 * Auto analysis only happens if stop tracing, thus:
+	 */
+	if (!params->common.stop_us && !params->common.stop_total_us)
+		params->no_aa = 1;
+
+	if (params->common.kernel_workload && params->common.user_workload)
+		fatal("--kernel-threads and --user-threads are mutually exclusive!");
+
+	/*
+	 * If auto-analysis or trace output is enabled, switch from BPF mode to
+	 * mixed mode
+	 */
+	if (params->mode == TRACING_MODE_BPF &&
+		(params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] ||
+		params->common.end_actions.present[ACTION_TRACE_OUTPUT] ||
+		!params->no_aa))
+		params->mode = TRACING_MODE_MIXED;
+
+	return &params->common;
+}
+
+/*
+ * rtla_usage - print rtla usage
+ */
+__noreturn static void rtla_usage(int err)
+{
+	int i;
+
+	static const char * const msg[] = {
+		"",
+		"rtla version " VERSION,
+		"",
+		"  usage: rtla COMMAND ...",
+		"",
+		"  commands:",
+		"     osnoise  - gives information about the operating system noise (osnoise)",
+		"     hwnoise  - gives information about hardware-related noise",
+		"     timerlat - measures the timer irq and thread latency",
+		"",
+		NULL,
+	};
+
+	for (i = 0; msg[i]; i++)
+		fprintf(stderr, "%s\n", msg[i]);
+	exit(err);
+}
+
+/*
+ * run_tool_command - try to run a rtla tool command
+ *
+ * It returns 0 if it fails. The tool's main will generally not
+ * return as they should call exit().
+ */
+int run_tool_command(int argc, char **argv, int start_position)
+{
+	if (strcmp(argv[start_position], "osnoise") == 0) {
+		osnoise_main(argc-start_position, &argv[start_position]);
+		goto ran;
+	} else if (strcmp(argv[start_position], "hwnoise") == 0) {
+		hwnoise_main(argc-start_position, &argv[start_position]);
+		goto ran;
+	} else if (strcmp(argv[start_position], "timerlat") == 0) {
+		timerlat_main(argc-start_position, &argv[start_position]);
+		goto ran;
+	}
+
+	return 0;
+ran:
+	return 1;
+}
+
+/* Set main as weak to allow overriding it for building unit test binary */
+#pragma weak main
+
+int main(int argc, char *argv[])
+{
+	int retval;
+
+	/* is it an alias? */
+	retval = run_tool_command(argc, argv, 0);
+	if (retval)
+		exit(0);
+
+	if (argc < 2)
+		goto usage;
+
+	if (strcmp(argv[1], "-h") == 0)
+		rtla_usage(129);
+	else if (strcmp(argv[1], "--help") == 0)
+		rtla_usage(129);
+
+	retval = run_tool_command(argc, argv, 1);
+	if (retval)
+		exit(0);
+
+usage:
+	rtla_usage(129);
+}
diff --git a/tools/tracing/rtla/src/cli.h b/tools/tracing/rtla/src/cli.h
new file mode 100644
index 000000000000..c49ccb3e92f5
--- /dev/null
+++ b/tools/tracing/rtla/src/cli.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+struct common_params *osnoise_top_parse_args(int argc, char **argv);
+struct common_params *osnoise_hist_parse_args(int argc, char **argv);
+struct common_params *timerlat_top_parse_args(int argc, char **argv);
+struct common_params *timerlat_hist_parse_args(int argc, char **argv);
diff --git a/tools/tracing/rtla/src/cli_p.h b/tools/tracing/rtla/src/cli_p.h
new file mode 100644
index 000000000000..3cea4f6e976e
--- /dev/null
+++ b/tools/tracing/rtla/src/cli_p.h
@@ -0,0 +1,670 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#ifndef RTLA_ALLOW_CLI_P_H
+#error "Private header file included outside of cli.c module"
+#endif
+
+#include <linux/kernel.h>
+#include <subcmd/parse-options.h>
+
+#include "cli.h"
+#include "osnoise.h"
+#include "timerlat.h"
+
+struct osnoise_cb_data {
+	struct osnoise_params *params;
+	char *trace_output;
+};
+
+struct timerlat_cb_data {
+	struct timerlat_params *params;
+	char *trace_output;
+};
+
+/*
+ * Macros for command line options common to all tools
+ *
+ * Note: Some of the options are common to both timerlat and osnoise, but
+ * have a slightly different meaning. Such options take additional arguments
+ * that have to be provided by the *_parse_args() function of the corresponding
+ * tool.
+ *
+ * All macros defined here assume the presence of a params variable of
+ * the corresponding tool type (i.e struct timerlat_params or struct osnoise_params)
+ * and a cb_data variable of the matching type.
+ */
+
+ #define RTLA_OPT_STOP(short, long, name) OPT_CALLBACK_FLAG(short, long, \
+	&params->common.stop_us, \
+	"us", \
+	"stop trace if " name " is higher than the argument in us", \
+	opt_llong_callback, PARSE_OPT_NOAUTONEG)
+
+#define RTLA_OPT_STOP_TOTAL(short, long, name) OPT_CALLBACK_FLAG(short, long, \
+	&params->common.stop_total_us, \
+	"us", \
+	"stop trace if " name " is higher than the argument in us", \
+	opt_llong_callback, PARSE_OPT_NOAUTONEG)
+
+#define RTLA_OPT_TRACE_OUTPUT(tracer, cb) OPT_CALLBACK_OPTARG('t', "trace", \
+	(const char **)&cb_data.trace_output, \
+	tracer "_trace.txt", \
+	"[file]", \
+	"save the stopped trace to [file|" tracer "_trace.txt]", \
+	cb)
+
+#define RTLA_OPT_CPUS OPT_CALLBACK('c', "cpus", &params->common, \
+	"cpu-list", \
+	"run the tracer only on the given cpus", \
+	opt_cpus_cb)
+
+#define RTLA_OPT_CGROUP OPT_CALLBACK_OPTARG('C', "cgroup", &params->common, \
+	"[cgroup_name]", NULL, \
+	"set cgroup, no argument means rtla's cgroup will be inherited", \
+	opt_cgroup_cb)
+
+#define RTLA_OPT_USER_THREADS OPT_CALLBACK_NOOPT('u', "user-threads", params, NULL, \
+	"use rtla user-space threads instead of kernel-space timerlat threads", \
+	opt_user_threads_cb)
+
+#define RTLA_OPT_KERNEL_THREADS OPT_BOOLEAN('k', "kernel-threads", \
+	&params->common.kernel_workload, \
+	"use timerlat kernel-space threads instead of rtla user-space threads")
+
+#define RTLA_OPT_USER_LOAD OPT_BOOLEAN('U', "user-load", &params->common.user_data, \
+	"enable timerlat for user-defined user-space workload")
+
+#define RTLA_OPT_DURATION OPT_CALLBACK('d', "duration", &params->common, \
+	"time[s|m|h|d]", \
+	"set the duration of the session", \
+	opt_duration_cb)
+
+#define RTLA_OPT_EVENT OPT_CALLBACK('e', "event", &params->common.events, \
+	"sys:event", \
+	"enable the <sys:event> in the trace instance, multiple -e are allowed", \
+	opt_event_cb)
+
+#define RTLA_OPT_HOUSEKEEPING OPT_CALLBACK('H', "house-keeping", &params->common, \
+	"cpu-list", \
+	"run rtla control threads only on the given cpus", \
+	opt_housekeeping_cb)
+
+#define RTLA_OPT_PRIORITY OPT_CALLBACK('P', "priority", &params->common, \
+	"o:prio|r:prio|f:prio|d:runtime:period", \
+	"set scheduling parameters", \
+	opt_priority_cb)
+
+#define RTLA_OPT_TRIGGER OPT_CALLBACK(0, "trigger", &params->common.events, \
+	"trigger", \
+	"enable a trace event trigger to the previous -e event", \
+	opt_trigger_cb)
+
+#define RTLA_OPT_FILTER OPT_CALLBACK(0, "filter", &params->common.events, \
+	"filter", \
+	"enable a trace event filter to the previous -e event", \
+	opt_filter_cb)
+
+#define RTLA_OPT_QUIET OPT_BOOLEAN('q', "quiet", &params->common.quiet, \
+	"print only a summary at the end")
+
+#define RTLA_OPT_TRACE_BUFFER_SIZE OPT_CALLBACK(0, "trace-buffer-size", \
+	&params->common.buffer_size, "kB", \
+	"set the per-cpu trace buffer size in kB", \
+	opt_int_callback)
+
+#define RTLA_OPT_WARM_UP OPT_CALLBACK(0, "warm-up", &params->common.warmup, "s", \
+	"let the workload run for s seconds before collecting data", \
+	opt_int_callback)
+
+#define RTLA_OPT_AUTO(cb) OPT_CALLBACK('a', "auto", &cb_data, "us", \
+	"set automatic trace mode, stopping the session if argument in us sample is hit", \
+	cb)
+
+#define RTLA_OPT_ON_THRESHOLD(threshold, cb) OPT_CALLBACK(0, "on-threshold", \
+	&params->common.threshold_actions, \
+	"action", \
+	"define action to be executed at " threshold " threshold, multiple are allowed", \
+	cb)
+
+#define RTLA_OPT_ON_END(cb) OPT_CALLBACK(0, "on-end", &params->common.end_actions, \
+	"action", \
+	"define action to be executed at measurement end, multiple are allowed", \
+	cb)
+
+#define RTLA_OPT_DEBUG OPT_BOOLEAN('D', "debug", &config_debug, \
+	"print debug info")
+
+/*
+ * Common callback functions for command line options
+ */
+
+static int opt_llong_callback(const struct option *opt, const char *arg, int unset)
+{
+	long long *value = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*value = get_llong_from_str((char *)arg);
+	return 0;
+}
+
+static int opt_int_callback(const struct option *opt, const char *arg, int unset)
+{
+	int *value = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	if (strtoi(arg, value))
+		return -1;
+
+	return 0;
+}
+
+static int opt_cpus_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct common_params *params = opt->value;
+	int retval;
+
+	if (unset || !arg)
+		return -1;
+
+	retval = parse_cpu_set((char *)arg, &params->monitored_cpus);
+	if (retval)
+		fatal("Invalid -c cpu list");
+	params->cpus = (char *)arg;
+
+	return 0;
+}
+
+static int opt_cgroup_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct common_params *params = opt->value;
+
+	if (unset)
+		return -1;
+
+	params->cgroup = 1;
+	params->cgroup_name = (char *)arg;
+	if (params->cgroup_name && params->cgroup_name[0] == '=')
+		/* Allow -C=<cgroup_name> next to -C[ ]<cgroup_name> */
+		++params->cgroup_name;
+
+	return 0;
+}
+
+static int opt_duration_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct common_params *params = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	params->duration = parse_seconds_duration((char *)arg);
+	if (!params->duration)
+		fatal("Invalid -d duration");
+
+	return 0;
+}
+
+static int opt_event_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct trace_events **events = opt->value;
+	struct trace_events *tevent;
+
+	if (unset || !arg)
+		return -1;
+
+	tevent = trace_event_alloc((char *)arg);
+	if (!tevent)
+		fatal("Error alloc trace event");
+
+	if (*events)
+		tevent->next = *events;
+	*events = tevent;
+
+	return 0;
+}
+
+static int opt_housekeeping_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct common_params *params = opt->value;
+	int retval;
+
+	if (unset || !arg)
+		return -1;
+
+	params->hk_cpus = 1;
+	retval = parse_cpu_set((char *)arg, &params->hk_cpu_set);
+	if (retval)
+		fatal("Error parsing house keeping CPUs");
+
+	return 0;
+}
+
+static int opt_priority_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct common_params *params = opt->value;
+	int retval;
+
+	if (unset || !arg)
+		return -1;
+
+	retval = parse_prio((char *)arg, &params->sched_param);
+	if (retval == -1)
+		fatal("Invalid -P priority");
+	params->set_sched = 1;
+
+	return 0;
+}
+
+static int opt_trigger_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct trace_events **events = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	if (!*events)
+		fatal("--trigger requires a previous -e");
+
+	trace_event_add_trigger(*events, (char *)arg);
+
+	return 0;
+}
+
+static int opt_filter_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct trace_events **events = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	if (!*events)
+		fatal("--filter requires a previous -e");
+
+	trace_event_add_filter(*events, (char *)arg);
+
+	return 0;
+}
+
+/*
+ * Macros for command line options specific to osnoise
+ */
+#define OSNOISE_OPT_PERIOD OPT_CALLBACK('p', "period", &params->period, "us", \
+	"osnoise period in us", \
+	opt_osnoise_period_cb)
+
+#define OSNOISE_OPT_RUNTIME OPT_CALLBACK('r', "runtime", &params->runtime, "us", \
+	"osnoise runtime in us", \
+	opt_osnoise_runtime_cb)
+
+#define OSNOISE_OPT_THRESHOLD OPT_CALLBACK('T', "threshold", &params->threshold, "us", \
+	"the minimum delta to be considered a noise", \
+	opt_llong_callback)
+
+/*
+ * Callback functions for command line options for osnoise tools
+ */
+
+static int opt_osnoise_auto_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct osnoise_cb_data *cb_data = opt->value;
+	struct osnoise_params *params = cb_data->params;
+	long long auto_thresh;
+
+	if (unset || !arg)
+		return -1;
+
+	auto_thresh = get_llong_from_str((char *)arg);
+	params->common.stop_us = auto_thresh;
+	params->threshold = 1;
+
+	if (!cb_data->trace_output)
+		cb_data->trace_output = "osnoise_trace.txt";
+
+	return 0;
+}
+
+static int opt_osnoise_period_cb(const struct option *opt, const char *arg, int unset)
+{
+	unsigned long long *period = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*period = get_llong_from_str((char *)arg);
+	if (*period > 10000000)
+		fatal("Period longer than 10 s");
+
+	return 0;
+}
+
+static int opt_osnoise_runtime_cb(const struct option *opt, const char *arg, int unset)
+{
+	unsigned long long *runtime = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*runtime = get_llong_from_str((char *)arg);
+	if (*runtime < 100)
+		fatal("Runtime shorter than 100 us");
+
+	return 0;
+}
+
+static int opt_osnoise_trace_output_cb(const struct option *opt, const char *arg, int unset)
+{
+	const char **trace_output = opt->value;
+
+	if (unset)
+		return -1;
+
+	if (!arg) {
+		*trace_output = "osnoise_trace.txt";
+	} else {
+		*trace_output = (char *)arg;
+		if (*trace_output && (*trace_output)[0] == '=')
+			/* Allow -t=<trace_output> next to -t[ ]<trace_output> */
+			++*trace_output;
+	}
+
+	return 0;
+}
+
+static int opt_osnoise_on_threshold_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct actions *actions = opt->value;
+	int retval;
+
+	if (unset || !arg)
+		return -1;
+
+	retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt");
+	if (retval)
+		fatal("Invalid action %s", arg);
+
+	return 0;
+}
+
+static int opt_osnoise_on_end_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct actions *actions = opt->value;
+	int retval;
+
+	if (unset || !arg)
+		return -1;
+
+	retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt");
+	if (retval)
+		fatal("Invalid action %s", arg);
+
+	return 0;
+}
+
+/*
+ * Macros for command line options specific to timerlat
+ */
+#define TIMERLAT_OPT_PERIOD OPT_CALLBACK('p', "period", &params->timerlat_period_us, "us", \
+	"timerlat period in us", \
+	opt_timerlat_period_cb)
+
+#define TIMERLAT_OPT_STACK OPT_CALLBACK('s', "stack", &params->print_stack, "us", \
+	"save the stack trace at the IRQ if a thread latency is higher than the argument in us", \
+	opt_llong_callback)
+
+#define TIMERLAT_OPT_NANO OPT_CALLBACK_NOOPT('n', "nano", params, NULL, \
+	"display data in nanoseconds", \
+	opt_nano_cb)
+
+#define TIMERLAT_OPT_DMA_LATENCY OPT_CALLBACK(0, "dma-latency", &params->dma_latency, "us", \
+	"set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency", \
+	opt_dma_latency_cb)
+
+#define TIMERLAT_OPT_DEEPEST_IDLE_STATE OPT_CALLBACK(0, "deepest-idle-state", \
+	&params->deepest_idle_state, "n", \
+	"only go down to idle state n on cpus used by timerlat to reduce exit from idle latency", \
+	opt_int_callback)
+
+#define TIMERLAT_OPT_AA_ONLY OPT_CALLBACK(0, "aa-only", params, "us", \
+	"stop if <us> latency is hit, only printing the auto analysis (reduces CPU usage)", \
+	opt_aa_only_cb)
+
+#define TIMERLAT_OPT_NO_AA OPT_BOOLEAN(0, "no-aa", &params->no_aa, \
+	"disable auto-analysis, reducing rtla timerlat cpu usage")
+
+#define TIMERLAT_OPT_DUMPS_TASKS OPT_BOOLEAN(0, "dump-tasks", &params->dump_tasks, \
+	"prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)")
+
+#define TIMERLAT_OPT_BPF_ACTION OPT_STRING(0, "bpf-action", &params->bpf_action_program, \
+	"program", \
+	"load and execute BPF program when latency threshold is exceeded")
+
+#define TIMERLAT_OPT_STACK_FORMAT OPT_CALLBACK(0, "stack-format", &params->stack_format, "format", \
+	"set the stack format (truncate, skip, full)", \
+	opt_stack_format_cb)
+
+/*
+ * Callback functions for command line options for timerlat tools
+ */
+
+static int opt_timerlat_period_cb(const struct option *opt, const char *arg, int unset)
+{
+	long long *period = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*period = get_llong_from_str((char *)arg);
+	if (*period > 1000000)
+		fatal("Period longer than 1 s");
+
+	return 0;
+}
+
+static int opt_timerlat_auto_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct timerlat_cb_data *cb_data = opt->value;
+	struct timerlat_params *params = cb_data->params;
+	long long auto_thresh;
+
+	if (unset || !arg)
+		return -1;
+
+	auto_thresh = get_llong_from_str((char *)arg);
+	params->common.stop_total_us = auto_thresh;
+	params->common.stop_us = auto_thresh;
+	params->print_stack = auto_thresh;
+
+	if (!cb_data->trace_output)
+		cb_data->trace_output = "timerlat_trace.txt";
+
+	return 0;
+}
+
+static int opt_dma_latency_cb(const struct option *opt, const char *arg, int unset)
+{
+	int *dma_latency = opt->value;
+	int retval;
+
+	if (unset || !arg)
+		return -1;
+
+	retval = strtoi((char *)arg, dma_latency);
+	if (retval)
+		fatal("Invalid -dma-latency %s", arg);
+	if (*dma_latency < 0 || *dma_latency > 10000)
+		fatal("--dma-latency needs to be >= 0 and <= 10000");
+
+	return 0;
+}
+
+static int opt_aa_only_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct timerlat_params *params = opt->value;
+	long long auto_thresh;
+
+	if (unset || !arg)
+		return -1;
+
+	auto_thresh = get_llong_from_str((char *)arg);
+	params->common.stop_total_us = auto_thresh;
+	params->common.stop_us = auto_thresh;
+	params->print_stack = auto_thresh;
+	params->common.aa_only = 1;
+
+	return 0;
+}
+
+static int opt_timerlat_trace_output_cb(const struct option *opt, const char *arg, int unset)
+{
+	const char **trace_output = opt->value;
+
+	if (unset)
+		return -1;
+
+	if (!arg) {
+		*trace_output = "timerlat_trace.txt";
+	} else {
+		*trace_output = (char *)arg;
+		if (*trace_output && (*trace_output)[0] == '=')
+			/* Allow -t=<trace_output> next to -t[ ]<trace_output> */
+			++*trace_output;
+	}
+
+	return 0;
+}
+
+static int opt_timerlat_on_threshold_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct actions *actions = opt->value;
+	int retval;
+
+	if (unset || !arg)
+		return -1;
+
+	retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt");
+	if (retval)
+		fatal("Invalid action %s", arg);
+
+	return 0;
+}
+
+static int opt_timerlat_on_end_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct actions *actions = opt->value;
+	int retval;
+
+	if (unset || !arg)
+		return -1;
+
+	retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt");
+	if (retval)
+		fatal("Invalid action %s", arg);
+
+	return 0;
+}
+
+static int opt_user_threads_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct timerlat_params *params = opt->value;
+
+	if (unset)
+		return -1;
+
+	params->common.user_workload = true;
+	params->common.user_data = true;
+
+	return 0;
+}
+
+static int opt_nano_cb(const struct option *opt, const char *arg, int unset)
+{
+	struct timerlat_params *params = opt->value;
+
+	if (unset)
+		return -1;
+
+	params->common.output_divisor = 1;
+
+	return 0;
+}
+
+static int opt_stack_format_cb(const struct option *opt, const char *arg, int unset)
+{
+	int *format = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*format = parse_stack_format((char *)arg);
+
+	if (*format == -1)
+		fatal("Invalid --stack-format option");
+
+	return 0;
+}
+
+/*
+ * Macros for command line options specific to histogram-based tools
+ */
+
+#define HIST_OPT_BUCKET_SIZE OPT_CALLBACK('b', "bucket-size", \
+	&params->common.hist.bucket_size, "N", \
+	"set the histogram bucket size (default 1)", \
+	opt_bucket_size_cb)
+
+#define HIST_OPT_ENTRIES OPT_CALLBACK('E', "entries", &params->common.hist.entries, "N", \
+	"set the number of entries of the histogram (default 256)", \
+	opt_entries_cb)
+
+#define HIST_OPT_NO_IRQ OPT_BOOLEAN_FLAG(0, "no-irq", &params->common.hist.no_irq, \
+	"ignore IRQ latencies", PARSE_OPT_NOAUTONEG)
+
+#define HIST_OPT_NO_THREAD OPT_BOOLEAN_FLAG(0, "no-thread", &params->common.hist.no_thread, \
+	"ignore thread latencies", PARSE_OPT_NOAUTONEG)
+
+#define HIST_OPT_NO_HEADER OPT_BOOLEAN(0, "no-header", &params->common.hist.no_header, \
+	"do not print header")
+
+#define HIST_OPT_NO_SUMMARY OPT_BOOLEAN(0, "no-summary", &params->common.hist.no_summary, \
+	"do not print summary")
+
+#define HIST_OPT_NO_INDEX OPT_BOOLEAN(0, "no-index", &params->common.hist.no_index, \
+	"do not print index")
+
+#define HIST_OPT_WITH_ZEROS OPT_BOOLEAN(0, "with-zeros", &params->common.hist.with_zeros, \
+	"print zero only entries")
+
+/* Histogram-specific callbacks */
+
+static int opt_bucket_size_cb(const struct option *opt, const char *arg, int unset)
+{
+	int *bucket_size = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*bucket_size = get_llong_from_str((char *)arg);
+	if (*bucket_size == 0 || *bucket_size >= 1000000)
+		fatal("Bucket size needs to be > 0 and <= 1000000");
+
+	return 0;
+}
+
+static int opt_entries_cb(const struct option *opt, const char *arg, int unset)
+{
+	int *entries = opt->value;
+
+	if (unset || !arg)
+		return -1;
+
+	*entries = get_llong_from_str((char *)arg);
+	if (*entries < 10 || *entries > 9999999)
+		fatal("Entries must be > 10 and < 10000000");
+
+	return 0;
+}
diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c
index effad523e8cf..d0a8a6edbf0c 100644
--- a/tools/tracing/rtla/src/common.c
+++ b/tools/tracing/rtla/src/common.c
@@ -5,7 +5,6 @@
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
-#include <getopt.h>
 #include <sys/sysinfo.h>
 
 #include "common.h"
@@ -57,114 +56,6 @@ static void unset_signals(struct common_params *params)
 	}
 }
 
-/*
- * getopt_auto - auto-generates optstring from long_options
- */
-int getopt_auto(int argc, char **argv, const struct option *long_opts)
-{
-	char opts[256];
-	int n = 0;
-
-	for (int i = 0; long_opts[i].name; i++) {
-		if (long_opts[i].val < 32 || long_opts[i].val > 127)
-			continue;
-
-		if (n + 4 >= sizeof(opts))
-			fatal("optstring buffer overflow");
-
-		opts[n++] = long_opts[i].val;
-
-		if (long_opts[i].has_arg == required_argument)
-			opts[n++] = ':';
-		else if (long_opts[i].has_arg == optional_argument) {
-			opts[n++] = ':';
-			opts[n++] = ':';
-		}
-	}
-
-	opts[n] = '\0';
-
-	return getopt_long(argc, argv, opts, long_opts, NULL);
-}
-
-/*
- * common_parse_options - parse common command line options
- *
- * @argc: argument count
- * @argv: argument vector
- * @common: common parameters structure
- *
- * Parse command line options that are common to all rtla tools.
- *
- * Returns: non zero if a common option was parsed, or 0
- * if the option should be handled by tool-specific parsing.
- */
-int common_parse_options(int argc, char **argv, struct common_params *common)
-{
-	struct trace_events *tevent;
-	int saved_state = optind;
-	int c;
-
-	static struct option long_options[] = {
-		{"cpus",                required_argument,      0, 'c'},
-		{"cgroup",              optional_argument,      0, 'C'},
-		{"debug",               no_argument,            0, 'D'},
-		{"duration",            required_argument,      0, 'd'},
-		{"event",               required_argument,      0, 'e'},
-		{"house-keeping",       required_argument,      0, 'H'},
-		{"priority",            required_argument,      0, 'P'},
-		{0, 0, 0, 0}
-	};
-
-	opterr = 0;
-	c = getopt_auto(argc, argv, long_options);
-	opterr = 1;
-
-	switch (c) {
-	case 'c':
-		if (parse_cpu_set(optarg, &common->monitored_cpus))
-			fatal("Invalid -c cpu list");
-		common->cpus = optarg;
-		break;
-	case 'C':
-		common->cgroup = 1;
-		common->cgroup_name = parse_optional_arg(argc, argv);
-		break;
-	case 'D':
-		config_debug = 1;
-		break;
-	case 'd':
-		common->duration = parse_seconds_duration(optarg);
-		if (!common->duration)
-			fatal("Invalid -d duration");
-		break;
-	case 'e':
-		tevent = trace_event_alloc(optarg);
-		if (!tevent)
-			fatal("Error alloc trace event");
-
-		if (common->events)
-			tevent->next = common->events;
-		common->events = tevent;
-		break;
-	case 'H':
-		common->hk_cpus = 1;
-		if (parse_cpu_set(optarg, &common->hk_cpu_set))
-			fatal("Error parsing house keeping CPUs");
-		break;
-	case 'P':
-		if (parse_prio(optarg, &common->sched_param) == -1)
-			fatal("Invalid -P priority");
-		common->set_sched = 1;
-		break;
-	default:
-		optind = saved_state;
-		return 0;
-	}
-
-	return c;
-}
-
 /*
  * common_apply_config - apply common configs to the initialized tool
  */
diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h
index eba40b6d9504..0dfca83bd726 100644
--- a/tools/tracing/rtla/src/common.h
+++ b/tools/tracing/rtla/src/common.h
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 #pragma once
 
-#include <getopt.h>
 #include "actions.h"
 #include "timerlat_u.h"
 #include "trace.h"
@@ -57,12 +56,12 @@ struct osnoise_context {
 extern volatile int stop_tracing;
 
 struct hist_params {
-	char			no_irq;
-	char			no_thread;
-	char			no_header;
-	char			no_summary;
-	char			no_index;
-	char			with_zeros;
+	bool			no_irq;
+	bool			no_thread;
+	bool			no_header;
+	bool			no_summary;
+	bool			no_index;
+	bool			with_zeros;
 	int			bucket_size;
 	int			entries;
 };
@@ -95,12 +94,12 @@ struct common_params {
 	/* Other parameters */
 	struct hist_params	hist;
 	int			output_divisor;
-	int			pretty_output;
-	int			quiet;
-	int			user_workload;
-	int			kernel_workload;
-	int			user_data;
-	int			aa_only;
+	bool			pretty_output;
+	bool			quiet;
+	bool			user_workload;
+	bool			kernel_workload;
+	bool			user_data;
+	bool			aa_only;
 
 	struct actions		threshold_actions;
 	struct actions		end_actions;
@@ -176,8 +175,6 @@ 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 getopt_auto(int argc, char **argv, const struct option *long_opts);
-int common_parse_options(int argc, char **argv, struct common_params *common);
 int common_apply_config(struct osnoise_tool *tool, struct common_params *params);
 int top_main_loop(struct osnoise_tool *tool);
 int hist_main_loop(struct osnoise_tool *tool);
diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c
index 2db3db155c44..e1e32898af2d 100644
--- a/tools/tracing/rtla/src/osnoise.c
+++ b/tools/tracing/rtla/src/osnoise.c
@@ -15,6 +15,8 @@
 #include <stdio.h>
 #include <sched.h>
 
+#include <linux/compiler.h>
+
 #include "osnoise.h"
 
 #define DEFAULT_SAMPLE_PERIOD	1000000			/* 1s */
@@ -1171,7 +1173,7 @@ int osnoise_enable(struct osnoise_tool *tool)
 	return 0;
 }
 
-static void osnoise_usage(int err)
+__noreturn static void osnoise_usage(int err)
 {
 	int i;
 
@@ -1209,7 +1211,7 @@ int osnoise_main(int argc, char *argv[])
 	}
 
 	if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
-		osnoise_usage(0);
+		osnoise_usage(129);
 	} else if (str_has_prefix(argv[1], "-")) {
 		/* the user skipped the tool, call the default one */
 		run_tool(&osnoise_top_ops, argc, argv);
@@ -1223,8 +1225,7 @@ int osnoise_main(int argc, char *argv[])
 	}
 
 usage:
-	osnoise_usage(1);
-	exit(1);
+	osnoise_usage(129);
 }
 
 int hwnoise_main(int argc, char *argv[])
diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c
index 8ad816b80265..dfa91d0681f8 100644
--- a/tools/tracing/rtla/src/osnoise_hist.c
+++ b/tools/tracing/rtla/src/osnoise_hist.c
@@ -4,7 +4,6 @@
  */
 
 #define _GNU_SOURCE
-#include <getopt.h>
 #include <stdlib.h>
 #include <string.h>
 #include <signal.h>
@@ -13,6 +12,7 @@
 #include <time.h>
 
 #include "osnoise.h"
+#include "cli.h"
 
 struct osnoise_hist_cpu {
 	int			*samples;
@@ -400,225 +400,6 @@ osnoise_print_stats(struct osnoise_tool *tool)
 	osnoise_report_missed_events(tool);
 }
 
-/*
- * osnoise_hist_usage - prints osnoise hist usage message
- */
-static void osnoise_hist_usage(void)
-{
-	static const char * const msg_start[] = {
-		"[-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
-		"	  [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
-		"	  [-c cpu-list] [-H cpu-list] [-P priority] [-b N] [-E N] [--no-header] [--no-summary] \\",
-		"	  [--no-index] [--with-zeros] [-C [cgroup_name]] [--warm-up]",
-		NULL,
-	};
-
-	static const char * const msg_opts[] = {
-		"	  -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
-		"	  -p/--period us: osnoise period in us",
-		"	  -r/--runtime us: osnoise runtime in us",
-		"	  -s/--stop us: stop trace if a single sample is higher than the argument in us",
-		"	  -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
-		"	  -T/--threshold us: the minimum delta to be considered a noise",
-		"	  -c/--cpus cpu-list: list of cpus to run osnoise threads",
-		"	  -H/--house-keeping cpus: run rtla control threads only on the given cpus",
-		"	  -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
-		"	  -d/--duration time[s|m|h|d]: duration of the session",
-		"	  -D/--debug: print debug info",
-		"	  -t/--trace [file]: save the stopped trace to [file|osnoise_trace.txt]",
-		"	  -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
-		"	     --filter <filter>: enable a trace event filter to the previous -e event",
-		"	     --trigger <trigger>: enable a trace event trigger to the previous -e event",
-		"	  -b/--bucket-size N: set the histogram bucket size (default 1)",
-		"	  -E/--entries N: set the number of entries of the histogram (default 256)",
-		"	     --no-header: do not print header",
-		"	     --no-summary: do not print summary",
-		"	     --no-index: do not print index",
-		"	     --with-zeros: print zero only entries",
-		"	  -P/--priority o:prio|r:prio|f:prio|d:runtime:period: set scheduling parameters",
-		"		o:prio - use SCHED_OTHER with prio",
-		"		r:prio - use SCHED_RR with prio",
-		"		f:prio - use SCHED_FIFO with prio",
-		"		d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
-		"						       in nanoseconds",
-		"	     --warm-up: let the workload run for s seconds before collecting data",
-		"	     --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
-		"	     --on-threshold <action>: define action to be executed at stop-total threshold, multiple are allowed",
-		"	     --on-end <action>: define action to be executed at measurement end, multiple are allowed",
-		NULL,
-	};
-
-	common_usage("osnoise", "hist", "a per-cpu histogram of the OS noise",
-		     msg_start, msg_opts);
-}
-
-/*
- * osnoise_hist_parse_args - allocs, parse and fill the cmd line parameters
- */
-static struct common_params
-*osnoise_hist_parse_args(int argc, char *argv[])
-{
-	struct osnoise_params *params;
-	int retval;
-	int c;
-	char *trace_output = NULL;
-
-	params = calloc_fatal(1, sizeof(*params));
-
-	actions_init(&params->common.threshold_actions);
-	actions_init(&params->common.end_actions);
-
-	/* display data in microseconds */
-	params->common.output_divisor = 1000;
-	params->common.hist.bucket_size = 1;
-	params->common.hist.entries = 256;
-
-	while (1) {
-		static struct option long_options[] = {
-			{"auto",		required_argument,	0, 'a'},
-			{"bucket-size",		required_argument,	0, 'b'},
-			{"entries",		required_argument,	0, 'E'},
-			{"help",		no_argument,		0, 'h'},
-			{"period",		required_argument,	0, 'p'},
-			{"runtime",		required_argument,	0, 'r'},
-			{"stop",		required_argument,	0, 's'},
-			{"stop-total",		required_argument,	0, 'S'},
-			{"trace",		optional_argument,	0, 't'},
-			{"threshold",		required_argument,	0, 'T'},
-			{"no-header",		no_argument,		0, '0'},
-			{"no-summary",		no_argument,		0, '1'},
-			{"no-index",		no_argument,		0, '2'},
-			{"with-zeros",		no_argument,		0, '3'},
-			{"trigger",		required_argument,	0, '4'},
-			{"filter",		required_argument,	0, '5'},
-			{"warm-up",		required_argument,	0, '6'},
-			{"trace-buffer-size",	required_argument,	0, '7'},
-			{"on-threshold",	required_argument,	0, '8'},
-			{"on-end",		required_argument,	0, '9'},
-			{0, 0, 0, 0}
-		};
-
-		if (common_parse_options(argc, argv, &params->common))
-			continue;
-
-		c = getopt_auto(argc, argv, long_options);
-
-		/* detect the end of the options. */
-		if (c == -1)
-			break;
-
-		switch (c) {
-		case 'a':
-			/* set sample stop to auto_thresh */
-			params->common.stop_us = get_llong_from_str(optarg);
-
-			/* set sample threshold to 1 */
-			params->threshold = 1;
-
-			/* set trace */
-			if (!trace_output)
-				trace_output = "osnoise_trace.txt";
-
-			break;
-		case 'b':
-			params->common.hist.bucket_size = get_llong_from_str(optarg);
-			if (params->common.hist.bucket_size == 0 ||
-			    params->common.hist.bucket_size >= 1000000)
-				fatal("Bucket size needs to be > 0 and <= 1000000");
-			break;
-		case 'E':
-			params->common.hist.entries = get_llong_from_str(optarg);
-			if (params->common.hist.entries < 10 ||
-			    params->common.hist.entries > 9999999)
-				fatal("Entries must be > 10 and < 9999999");
-			break;
-		case 'h':
-		case '?':
-			osnoise_hist_usage();
-			break;
-		case 'p':
-			params->period = get_llong_from_str(optarg);
-			if (params->period > 10000000)
-				fatal("Period longer than 10 s");
-			break;
-		case 'r':
-			params->runtime = get_llong_from_str(optarg);
-			if (params->runtime < 100)
-				fatal("Runtime shorter than 100 us");
-			break;
-		case 's':
-			params->common.stop_us = get_llong_from_str(optarg);
-			break;
-		case 'S':
-			params->common.stop_total_us = get_llong_from_str(optarg);
-			break;
-		case 'T':
-			params->threshold = get_llong_from_str(optarg);
-			break;
-		case 't':
-			trace_output = parse_optional_arg(argc, argv);
-			if (!trace_output)
-				trace_output = "osnoise_trace.txt";
-			break;
-		case '0': /* no header */
-			params->common.hist.no_header = 1;
-			break;
-		case '1': /* no summary */
-			params->common.hist.no_summary = 1;
-			break;
-		case '2': /* no index */
-			params->common.hist.no_index = 1;
-			break;
-		case '3': /* with zeros */
-			params->common.hist.with_zeros = 1;
-			break;
-		case '4': /* trigger */
-			if (params->common.events)
-				trace_event_add_trigger(params->common.events, optarg);
-			else
-				fatal("--trigger requires a previous -e");
-			break;
-		case '5': /* filter */
-			if (params->common.events)
-				trace_event_add_filter(params->common.events, optarg);
-			else
-				fatal("--filter requires a previous -e");
-			break;
-		case '6':
-			params->common.warmup = get_llong_from_str(optarg);
-			break;
-		case '7':
-			params->common.buffer_size = get_llong_from_str(optarg);
-			break;
-		case '8':
-			retval = actions_parse(&params->common.threshold_actions, optarg,
-					       "osnoise_trace.txt");
-			if (retval)
-				fatal("Invalid action %s", optarg);
-			break;
-		case '9':
-			retval = actions_parse(&params->common.end_actions, optarg,
-					       "osnoise_trace.txt");
-			if (retval)
-				fatal("Invalid action %s", optarg);
-			break;
-		default:
-			fatal("Invalid option");
-		}
-	}
-
-	if (trace_output)
-		actions_add_trace_output(&params->common.threshold_actions, trace_output);
-
-	if (geteuid())
-		fatal("rtla needs root permission");
-
-	if (params->common.hist.no_index && !params->common.hist.with_zeros)
-		fatal("no-index set and with-zeros not set - it does not make sense");
-
-	return &params->common;
-}
-
 /*
  * osnoise_hist_apply_config - apply the hist configs to the initialized tool
  */
diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c
index 244bdce022ad..512a6299cb01 100644
--- a/tools/tracing/rtla/src/osnoise_top.c
+++ b/tools/tracing/rtla/src/osnoise_top.c
@@ -4,7 +4,6 @@
  */
 
 #define _GNU_SOURCE
-#include <getopt.h>
 #include <stdlib.h>
 #include <string.h>
 #include <signal.h>
@@ -13,6 +12,7 @@
 #include <time.h>
 
 #include "osnoise.h"
+#include "cli.h"
 
 struct osnoise_top_cpu {
 	unsigned long long	sum_runtime;
@@ -245,204 +245,6 @@ osnoise_print_stats(struct osnoise_tool *top)
 	osnoise_report_missed_events(top);
 }
 
-/*
- * osnoise_top_usage - prints osnoise top usage message
- */
-static void osnoise_top_usage(struct osnoise_params *params)
-{
-	const char *tool, *mode, *desc;
-
-	static const char * const msg_start[] = {
-		"[-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
-		"	  [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
-		"	  [-c cpu-list] [-H cpu-list] [-P priority] [-C [cgroup_name]] [--warm-up s]",
-		NULL,
-	};
-
-	static const char * const msg_opts[] = {
-		"	  -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
-		"	  -p/--period us: osnoise period in us",
-		"	  -r/--runtime us: osnoise runtime in us",
-		"	  -s/--stop us: stop trace if a single sample is higher than the argument in us",
-		"	  -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
-		"	  -T/--threshold us: the minimum delta to be considered a noise",
-		"	  -c/--cpus cpu-list: list of cpus to run osnoise threads",
-		"	  -H/--house-keeping cpus: run rtla control threads only on the given cpus",
-		"	  -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
-		"	  -d/--duration time[s|m|h|d]: duration of the session",
-		"	  -D/--debug: print debug info",
-		"	  -t/--trace [file]: save the stopped trace to [file|osnoise_trace.txt]",
-		"	  -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
-		"	     --filter <filter>: enable a trace event filter to the previous -e event",
-		"	     --trigger <trigger>: enable a trace event trigger to the previous -e event",
-		"	  -q/--quiet print only a summary at the end",
-		"	  -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
-		"		o:prio - use SCHED_OTHER with prio",
-		"		r:prio - use SCHED_RR with prio",
-		"		f:prio - use SCHED_FIFO with prio",
-		"		d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
-		"						       in nanoseconds",
-		"	     --warm-up s: let the workload run for s seconds before collecting data",
-		"	     --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
-		"	     --on-threshold <action>: define action to be executed at stop-total threshold, multiple are allowed",
-		"	     --on-end: define action to be executed at measurement end, multiple are allowed",
-		NULL,
-	};
-
-	if (params->mode == MODE_OSNOISE) {
-		tool = "osnoise";
-		mode = "top";
-		desc = "a per-cpu summary of the OS noise";
-	} else {
-		tool = "hwnoise";
-		mode = "";
-		desc = "a summary of hardware-related noise";
-	}
-
-	common_usage(tool, mode, desc, msg_start, msg_opts);
-}
-
-/*
- * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
- */
-struct common_params *osnoise_top_parse_args(int argc, char **argv)
-{
-	struct osnoise_params *params;
-	int retval;
-	int c;
-	char *trace_output = NULL;
-
-	params = calloc_fatal(1, sizeof(*params));
-
-	actions_init(&params->common.threshold_actions);
-	actions_init(&params->common.end_actions);
-
-	if (strcmp(argv[0], "hwnoise") == 0) {
-		params->mode = MODE_HWNOISE;
-		/*
-		 * Reduce CPU usage for 75% to avoid killing the system.
-		 */
-		params->runtime = 750000;
-		params->period = 1000000;
-	}
-
-	while (1) {
-		static struct option long_options[] = {
-			{"auto",		required_argument,	0, 'a'},
-			{"help",		no_argument,		0, 'h'},
-			{"period",		required_argument,	0, 'p'},
-			{"quiet",		no_argument,		0, 'q'},
-			{"runtime",		required_argument,	0, 'r'},
-			{"stop",		required_argument,	0, 's'},
-			{"stop-total",		required_argument,	0, 'S'},
-			{"threshold",		required_argument,	0, 'T'},
-			{"trace",		optional_argument,	0, 't'},
-			{"trigger",		required_argument,	0, '0'},
-			{"filter",		required_argument,	0, '1'},
-			{"warm-up",		required_argument,	0, '2'},
-			{"trace-buffer-size",	required_argument,	0, '3'},
-			{"on-threshold",	required_argument,	0, '4'},
-			{"on-end",		required_argument,	0, '5'},
-			{0, 0, 0, 0}
-		};
-
-		if (common_parse_options(argc, argv, &params->common))
-			continue;
-
-		c = getopt_auto(argc, argv, long_options);
-
-		/* Detect the end of the options. */
-		if (c == -1)
-			break;
-
-		switch (c) {
-		case 'a':
-			/* set sample stop to auto_thresh */
-			params->common.stop_us = get_llong_from_str(optarg);
-
-			/* set sample threshold to 1 */
-			params->threshold = 1;
-
-			/* set trace */
-			if (!trace_output)
-				trace_output = "osnoise_trace.txt";
-
-			break;
-		case 'h':
-		case '?':
-			osnoise_top_usage(params);
-			break;
-		case 'p':
-			params->period = get_llong_from_str(optarg);
-			if (params->period > 10000000)
-				fatal("Period longer than 10 s");
-			break;
-		case 'q':
-			params->common.quiet = 1;
-			break;
-		case 'r':
-			params->runtime = get_llong_from_str(optarg);
-			if (params->runtime < 100)
-				fatal("Runtime shorter than 100 us");
-			break;
-		case 's':
-			params->common.stop_us = get_llong_from_str(optarg);
-			break;
-		case 'S':
-			params->common.stop_total_us = get_llong_from_str(optarg);
-			break;
-		case 't':
-			trace_output = parse_optional_arg(argc, argv);
-			if (!trace_output)
-				trace_output = "osnoise_trace.txt";
-			break;
-		case 'T':
-			params->threshold = get_llong_from_str(optarg);
-			break;
-		case '0': /* trigger */
-			if (params->common.events)
-				trace_event_add_trigger(params->common.events, optarg);
-			else
-				fatal("--trigger requires a previous -e");
-			break;
-		case '1': /* filter */
-			if (params->common.events)
-				trace_event_add_filter(params->common.events, optarg);
-			else
-				fatal("--filter requires a previous -e");
-			break;
-		case '2':
-			params->common.warmup = get_llong_from_str(optarg);
-			break;
-		case '3':
-			params->common.buffer_size = get_llong_from_str(optarg);
-			break;
-		case '4':
-			retval = actions_parse(&params->common.threshold_actions, optarg,
-					       "osnoise_trace.txt");
-			if (retval)
-				fatal("Invalid action %s", optarg);
-			break;
-		case '5':
-			retval = actions_parse(&params->common.end_actions, optarg,
-					       "osnoise_trace.txt");
-			if (retval)
-				fatal("Invalid action %s", optarg);
-			break;
-		default:
-			fatal("Invalid option");
-		}
-	}
-
-	if (trace_output)
-		actions_add_trace_output(&params->common.threshold_actions, trace_output);
-
-	if (geteuid())
-		fatal("osnoise needs root permission");
-
-	return &params->common;
-}
-
 /*
  * osnoise_top_apply_config - apply the top configs to the initialized tool
  */
diff --git a/tools/tracing/rtla/src/rtla.c b/tools/tracing/rtla/src/rtla.c
deleted file mode 100644
index f4342ca684c3..000000000000
--- a/tools/tracing/rtla/src/rtla.c
+++ /dev/null
@@ -1,92 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
- */
-
-#include <getopt.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include "osnoise.h"
-#include "timerlat.h"
-
-/*
- * rtla_usage - print rtla usage
- */
-static void rtla_usage(int err)
-{
-	int i;
-
-	static const char *msg[] = {
-		"",
-		"rtla version " VERSION,
-		"",
-		"  usage: rtla COMMAND ...",
-		"",
-		"  commands:",
-		"     osnoise  - gives information about the operating system noise (osnoise)",
-		"     hwnoise  - gives information about hardware-related noise",
-		"     timerlat - measures the timer irq and thread latency",
-		"",
-		NULL,
-	};
-
-	for (i = 0; msg[i]; i++)
-		fprintf(stderr, "%s\n", msg[i]);
-	exit(err);
-}
-
-/*
- * run_tool_command - try to run a rtla tool command
- *
- * It returns 0 if it fails. The tool's main will generally not
- * return as they should call exit().
- */
-int run_tool_command(int argc, char **argv, int start_position)
-{
-	if (strcmp(argv[start_position], "osnoise") == 0) {
-		osnoise_main(argc-start_position, &argv[start_position]);
-		goto ran;
-	} else if (strcmp(argv[start_position], "hwnoise") == 0) {
-		hwnoise_main(argc-start_position, &argv[start_position]);
-		goto ran;
-	} else if (strcmp(argv[start_position], "timerlat") == 0) {
-		timerlat_main(argc-start_position, &argv[start_position]);
-		goto ran;
-	}
-
-	return 0;
-ran:
-	return 1;
-}
-
-/* Set main as weak to allow overriding it for building unit test binary */
-#pragma weak main
-
-int main(int argc, char *argv[])
-{
-	int retval;
-
-	/* is it an alias? */
-	retval = run_tool_command(argc, argv, 0);
-	if (retval)
-		exit(0);
-
-	if (argc < 2)
-		goto usage;
-
-	if (strcmp(argv[1], "-h") == 0) {
-		rtla_usage(0);
-	} else if (strcmp(argv[1], "--help") == 0) {
-		rtla_usage(0);
-	}
-
-	retval = run_tool_command(argc, argv, 1);
-	if (retval)
-		exit(0);
-
-usage:
-	rtla_usage(1);
-	exit(1);
-}
diff --git a/tools/tracing/rtla/src/timerlat.c b/tools/tracing/rtla/src/timerlat.c
index 637f68d684f5..f990c8365776 100644
--- a/tools/tracing/rtla/src/timerlat.c
+++ b/tools/tracing/rtla/src/timerlat.c
@@ -13,6 +13,8 @@
 #include <stdio.h>
 #include <sched.h>
 
+#include <linux/compiler.h>
+
 #include "timerlat.h"
 #include "timerlat_aa.h"
 #include "timerlat_bpf.h"
@@ -231,7 +233,7 @@ void timerlat_free(struct osnoise_tool *tool)
 	free_cpu_idle_disable_states();
 }
 
-static void timerlat_usage(int err)
+__noreturn static void timerlat_usage(int err)
 {
 	int i;
 
@@ -269,7 +271,7 @@ int timerlat_main(int argc, char *argv[])
 	}
 
 	if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
-		timerlat_usage(0);
+		timerlat_usage(129);
 	} else if (str_has_prefix(argv[1], "-")) {
 		/* the user skipped the tool, call the default one */
 		run_tool(&timerlat_top_ops, argc, argv);
@@ -283,6 +285,5 @@ int timerlat_main(int argc, char *argv[])
 	}
 
 usage:
-	timerlat_usage(1);
-	exit(1);
+	timerlat_usage(129);
 }
diff --git a/tools/tracing/rtla/src/timerlat.h b/tools/tracing/rtla/src/timerlat.h
index 364203a29abd..37a808f1611e 100644
--- a/tools/tracing/rtla/src/timerlat.h
+++ b/tools/tracing/rtla/src/timerlat.h
@@ -23,8 +23,8 @@ struct timerlat_params {
 	long long		timerlat_period_us;
 	long long		print_stack;
 	int			dma_latency;
-	int			no_aa;
-	int			dump_tasks;
+	bool			no_aa;
+	bool			dump_tasks;
 	int			deepest_idle_state;
 	enum timerlat_tracing_mode mode;
 	const char		*bpf_action_program;
diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c
index 3a15a85b7d72..df7b1398a966 100644
--- a/tools/tracing/rtla/src/timerlat_hist.c
+++ b/tools/tracing/rtla/src/timerlat_hist.c
@@ -4,7 +4,6 @@
  */
 
 #define _GNU_SOURCE
-#include <getopt.h>
 #include <stdlib.h>
 #include <string.h>
 #include <signal.h>
@@ -17,6 +16,7 @@
 #include "timerlat.h"
 #include "timerlat_aa.h"
 #include "timerlat_bpf.h"
+#include "cli.h"
 #include "common.h"
 
 struct timerlat_hist_cpu {
@@ -685,321 +685,6 @@ timerlat_print_stats(struct osnoise_tool *tool)
 	osnoise_report_missed_events(tool);
 }
 
-/*
- * timerlat_hist_usage - prints timerlat top usage message
- */
-static void timerlat_hist_usage(void)
-{
-	static const char * const msg_start[] = {
-		"[-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\",
-		"         [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
-		"	  [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] \\",
-		"	  [--no-index] [--with-zeros] [--dma-latency us] [-C [cgroup_name]] [--no-aa] [--dump-tasks] [-u|-k]",
-		"	  [--warm-up s] [--deepest-idle-state n]",
-		NULL,
-	};
-
-	static const char * const msg_opts[] = {
-		"	  -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit",
-		"	  -p/--period us: timerlat period in us",
-		"	  -i/--irq us: stop trace if the irq latency is higher than the argument in us",
-		"	  -T/--thread us: stop trace if the thread latency is higher than the argument in us",
-		"	  -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us",
-		"	  -c/--cpus cpus: run the tracer only on the given cpus",
-		"	  -H/--house-keeping cpus: run rtla control threads only on the given cpus",
-		"	  -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
-		"	  -d/--duration time[m|h|d]: duration of the session in seconds",
-		"	     --dump-tasks: prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)",
-		"	  -D/--debug: print debug info",
-		"	  -t/--trace [file]: save the stopped trace to [file|timerlat_trace.txt]",
-		"	  -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
-		"	     --filter <filter>: enable a trace event filter to the previous -e event",
-		"	     --trigger <trigger>: enable a trace event trigger to the previous -e event",
-		"	  -n/--nano: display data in nanoseconds",
-		"	     --no-aa: disable auto-analysis, reducing rtla timerlat cpu usage",
-		"	  -b/--bucket-size N: set the histogram bucket size (default 1)",
-		"	  -E/--entries N: set the number of entries of the histogram (default 256)",
-		"	     --no-irq: ignore IRQ latencies",
-		"	     --no-thread: ignore thread latencies",
-		"	     --no-header: do not print header",
-		"	     --no-summary: do not print summary",
-		"	     --no-index: do not print index",
-		"	     --with-zeros: print zero only entries",
-		"	     --dma-latency us: set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency",
-		"	  -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
-		"		o:prio - use SCHED_OTHER with prio",
-		"		r:prio - use SCHED_RR with prio",
-		"		f:prio - use SCHED_FIFO with prio",
-		"		d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
-		"						       in nanoseconds",
-		"	  -u/--user-threads: use rtla user-space threads instead of kernel-space timerlat threads",
-		"	  -k/--kernel-threads: use timerlat kernel-space threads instead of rtla user-space threads",
-		"	  -U/--user-load: enable timerlat for user-defined user-space workload",
-		"	     --warm-up s: let the workload run for s seconds before collecting data",
-		"	     --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
-		"	     --deepest-idle-state n: only go down to idle state n on cpus used by timerlat to reduce exit from idle latency",
-		"	     --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
-		"	     --on-end <action>: define action to be executed at measurement end, multiple are allowed",
-		"	     --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
-		"	     --stack-format <format>: set the stack format (truncate, skip, full)",
-		NULL,
-	};
-
-	common_usage("timerlat", "hist", "a per-cpu histogram of the timer latency",
-		     msg_start, msg_opts);
-}
-
-/*
- * timerlat_hist_parse_args - allocs, parse and fill the cmd line parameters
- */
-static struct common_params
-*timerlat_hist_parse_args(int argc, char *argv[])
-{
-	struct timerlat_params *params;
-	int auto_thresh;
-	int retval;
-	int c;
-	char *trace_output = NULL;
-
-	params = calloc_fatal(1, sizeof(*params));
-
-	actions_init(&params->common.threshold_actions);
-	actions_init(&params->common.end_actions);
-
-	/* disabled by default */
-	params->dma_latency = -1;
-
-	/* disabled by default */
-	params->deepest_idle_state = -2;
-
-	/* display data in microseconds */
-	params->common.output_divisor = 1000;
-	params->common.hist.bucket_size = 1;
-	params->common.hist.entries = 256;
-
-	/* default to BPF mode */
-	params->mode = TRACING_MODE_BPF;
-
-	/* default to truncate stack format */
-	params->stack_format = STACK_FORMAT_TRUNCATE;
-
-	while (1) {
-		static struct option long_options[] = {
-			{"auto",		required_argument,	0, 'a'},
-			{"bucket-size",		required_argument,	0, 'b'},
-			{"entries",		required_argument,	0, 'E'},
-			{"help",		no_argument,		0, 'h'},
-			{"irq",			required_argument,	0, 'i'},
-			{"nano",		no_argument,		0, 'n'},
-			{"period",		required_argument,	0, 'p'},
-			{"stack",		required_argument,	0, 's'},
-			{"thread",		required_argument,	0, 'T'},
-			{"trace",		optional_argument,	0, 't'},
-			{"user-threads",	no_argument,		0, 'u'},
-			{"kernel-threads",	no_argument,		0, 'k'},
-			{"user-load",		no_argument,		0, 'U'},
-			{"no-irq",		no_argument,		0, '0'},
-			{"no-thread",		no_argument,		0, '1'},
-			{"no-header",		no_argument,		0, '2'},
-			{"no-summary",		no_argument,		0, '3'},
-			{"no-index",		no_argument,		0, '4'},
-			{"with-zeros",		no_argument,		0, '5'},
-			{"trigger",		required_argument,	0, '6'},
-			{"filter",		required_argument,	0, '7'},
-			{"dma-latency",		required_argument,	0, '8'},
-			{"no-aa",		no_argument,		0, '9'},
-			{"dump-tasks",		no_argument,		0, '\1'},
-			{"warm-up",		required_argument,	0, '\2'},
-			{"trace-buffer-size",	required_argument,	0, '\3'},
-			{"deepest-idle-state",	required_argument,	0, '\4'},
-			{"on-threshold",	required_argument,	0, '\5'},
-			{"on-end",		required_argument,	0, '\6'},
-			{"bpf-action",		required_argument,	0, '\7'},
-			{"stack-format",	required_argument,	0, '\10'},
-			{0, 0, 0, 0}
-		};
-
-		if (common_parse_options(argc, argv, &params->common))
-			continue;
-
-		c = getopt_auto(argc, argv, long_options);
-
-		/* detect the end of the options. */
-		if (c == -1)
-			break;
-
-		switch (c) {
-		case 'a':
-			auto_thresh = get_llong_from_str(optarg);
-
-			/* set thread stop to auto_thresh */
-			params->common.stop_total_us = auto_thresh;
-			params->common.stop_us = auto_thresh;
-
-			/* get stack trace */
-			params->print_stack = auto_thresh;
-
-			/* set trace */
-			if (!trace_output)
-				trace_output = "timerlat_trace.txt";
-
-			break;
-		case 'b':
-			params->common.hist.bucket_size = get_llong_from_str(optarg);
-			if (params->common.hist.bucket_size == 0 ||
-			    params->common.hist.bucket_size >= 1000000)
-				fatal("Bucket size needs to be > 0 and <= 1000000");
-			break;
-		case 'E':
-			params->common.hist.entries = get_llong_from_str(optarg);
-			if (params->common.hist.entries < 10 ||
-			    params->common.hist.entries > 9999999)
-				fatal("Entries must be > 10 and < 9999999");
-			break;
-		case 'h':
-		case '?':
-			timerlat_hist_usage();
-			break;
-		case 'i':
-			params->common.stop_us = get_llong_from_str(optarg);
-			break;
-		case 'k':
-			params->common.kernel_workload = 1;
-			break;
-		case 'n':
-			params->common.output_divisor = 1;
-			break;
-		case 'p':
-			params->timerlat_period_us = get_llong_from_str(optarg);
-			if (params->timerlat_period_us > 1000000)
-				fatal("Period longer than 1 s");
-			break;
-		case 's':
-			params->print_stack = get_llong_from_str(optarg);
-			break;
-		case 'T':
-			params->common.stop_total_us = get_llong_from_str(optarg);
-			break;
-		case 't':
-			trace_output = parse_optional_arg(argc, argv);
-			if (!trace_output)
-				trace_output = "timerlat_trace.txt";
-			break;
-		case 'u':
-			params->common.user_workload = 1;
-			/* fallback: -u implies in -U */
-		case 'U':
-			params->common.user_data = 1;
-			break;
-		case '0': /* no irq */
-			params->common.hist.no_irq = 1;
-			break;
-		case '1': /* no thread */
-			params->common.hist.no_thread = 1;
-			break;
-		case '2': /* no header */
-			params->common.hist.no_header = 1;
-			break;
-		case '3': /* no summary */
-			params->common.hist.no_summary = 1;
-			break;
-		case '4': /* no index */
-			params->common.hist.no_index = 1;
-			break;
-		case '5': /* with zeros */
-			params->common.hist.with_zeros = 1;
-			break;
-		case '6': /* trigger */
-			if (params->common.events)
-				trace_event_add_trigger(params->common.events, optarg);
-			else
-				fatal("--trigger requires a previous -e");
-			break;
-		case '7': /* filter */
-			if (params->common.events)
-				trace_event_add_filter(params->common.events, optarg);
-			else
-				fatal("--filter requires a previous -e");
-			break;
-		case '8':
-			params->dma_latency = get_llong_from_str(optarg);
-			if (params->dma_latency < 0 || params->dma_latency > 10000)
-				fatal("--dma-latency needs to be >= 0 and < 10000");
-			break;
-		case '9':
-			params->no_aa = 1;
-			break;
-		case '\1':
-			params->dump_tasks = 1;
-			break;
-		case '\2':
-			params->common.warmup = get_llong_from_str(optarg);
-			break;
-		case '\3':
-			params->common.buffer_size = get_llong_from_str(optarg);
-			break;
-		case '\4':
-			params->deepest_idle_state = get_llong_from_str(optarg);
-			break;
-		case '\5':
-			retval = actions_parse(&params->common.threshold_actions, optarg,
-					       "timerlat_trace.txt");
-			if (retval)
-				fatal("Invalid action %s", optarg);
-			break;
-		case '\6':
-			retval = actions_parse(&params->common.end_actions, optarg,
-					       "timerlat_trace.txt");
-			if (retval)
-				fatal("Invalid action %s", optarg);
-			break;
-		case '\7':
-			params->bpf_action_program = optarg;
-			break;
-		case '\10':
-			params->stack_format = parse_stack_format(optarg);
-			if (params->stack_format == -1)
-				fatal("Invalid --stack-format option");
-			break;
-		default:
-			fatal("Invalid option");
-		}
-	}
-
-	if (trace_output)
-		actions_add_trace_output(&params->common.threshold_actions, trace_output);
-
-	if (geteuid())
-		fatal("rtla needs root permission");
-
-	if (params->common.hist.no_irq && params->common.hist.no_thread)
-		fatal("no-irq and no-thread set, there is nothing to do here");
-
-	if (params->common.hist.no_index && !params->common.hist.with_zeros)
-		fatal("no-index set with with-zeros is not set - it does not make sense");
-
-	/*
-	 * Auto analysis only happens if stop tracing, thus:
-	 */
-	if (!params->common.stop_us && !params->common.stop_total_us)
-		params->no_aa = 1;
-
-	if (params->common.kernel_workload && params->common.user_workload)
-		fatal("--kernel-threads and --user-threads are mutually exclusive!");
-
-	/*
-	 * If auto-analysis or trace output is enabled, switch from BPF mode to
-	 * mixed mode
-	 */
-	if (params->mode == TRACING_MODE_BPF &&
-	    (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] ||
-	     params->common.end_actions.present[ACTION_TRACE_OUTPUT] ||
-	     !params->no_aa))
-		params->mode = TRACING_MODE_MIXED;
-
-	return &params->common;
-}
-
 /*
  * timerlat_hist_apply_config - apply the hist configs to the initialized tool
  */
diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c
index e3a374005690..18e1071a2e24 100644
--- a/tools/tracing/rtla/src/timerlat_top.c
+++ b/tools/tracing/rtla/src/timerlat_top.c
@@ -4,7 +4,6 @@
  */
 
 #define _GNU_SOURCE
-#include <getopt.h>
 #include <stdlib.h>
 #include <string.h>
 #include <signal.h>
@@ -17,6 +16,7 @@
 #include "timerlat.h"
 #include "timerlat_aa.h"
 #include "timerlat_bpf.h"
+#include "cli.h"
 #include "common.h"
 
 struct timerlat_top_cpu {
@@ -459,290 +459,6 @@ timerlat_print_stats(struct osnoise_tool *top)
 	osnoise_report_missed_events(top);
 }
 
-/*
- * timerlat_top_usage - prints timerlat top usage message
- */
-static void timerlat_top_usage(void)
-{
-	static const char *const msg_start[] = {
-		"[-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\",
-		"	  [[-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
-		"	  [-P priority] [--dma-latency us] [--aa-only us] [-C [cgroup_name]] [--dump-tasks] [-u|-k] [--warm-up s]\\",
-		"	  [--deepest-idle-state n]",
-		NULL,
-	};
-
-	static const char *const msg_opts[] = {
-		"	  -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit",
-		"	     --aa-only us: stop if <us> latency is hit, only printing the auto analysis (reduces CPU usage)",
-		"	  -p/--period us: timerlat period in us",
-		"	  -i/--irq us: stop trace if the irq latency is higher than the argument in us",
-		"	  -T/--thread us: stop trace if the thread latency is higher than the argument in us",
-		"	  -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us",
-		"	  -c/--cpus cpus: run the tracer only on the given cpus",
-		"	  -H/--house-keeping cpus: run rtla control threads only on the given cpus",
-		"	  -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
-		"	  -d/--duration time[s|m|h|d]: duration of the session",
-		"	  -D/--debug: print debug info",
-		"	     --dump-tasks: prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)",
-		"	  -t/--trace [file]: save the stopped trace to [file|timerlat_trace.txt]",
-		"	  -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
-		"	     --filter <command>: enable a trace event filter to the previous -e event",
-		"	     --trigger <command>: enable a trace event trigger to the previous -e event",
-		"	  -n/--nano: display data in nanoseconds",
-		"	     --no-aa: disable auto-analysis, reducing rtla timerlat cpu usage",
-		"	  -q/--quiet print only a summary at the end",
-		"	     --dma-latency us: set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency",
-		"	  -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
-		"		o:prio - use SCHED_OTHER with prio",
-		"		r:prio - use SCHED_RR with prio",
-		"		f:prio - use SCHED_FIFO with prio",
-		"		d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
-		"						       in nanoseconds",
-		"	  -u/--user-threads: use rtla user-space threads instead of kernel-space timerlat threads",
-		"	  -k/--kernel-threads: use timerlat kernel-space threads instead of rtla user-space threads",
-		"	  -U/--user-load: enable timerlat for user-defined user-space workload",
-		"	     --warm-up s: let the workload run for s seconds before collecting data",
-		"	     --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
-		"	     --deepest-idle-state n: only go down to idle state n on cpus used by timerlat to reduce exit from idle latency",
-		"	     --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
-		"	     --on-end: define action to be executed at measurement end, multiple are allowed",
-		"	     --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
-		"	     --stack-format <format>: set the stack format (truncate, skip, full)",
-		NULL,
-	};
-
-	common_usage("timerlat", "top", "a per-cpu summary of the timer latency",
-		     msg_start, msg_opts);
-}
-
-/*
- * timerlat_top_parse_args - allocs, parse and fill the cmd line parameters
- */
-static struct common_params
-*timerlat_top_parse_args(int argc, char **argv)
-{
-	struct timerlat_params *params;
-	long long auto_thresh;
-	int retval;
-	int c;
-	char *trace_output = NULL;
-
-	params = calloc_fatal(1, sizeof(*params));
-
-	actions_init(&params->common.threshold_actions);
-	actions_init(&params->common.end_actions);
-
-	/* disabled by default */
-	params->dma_latency = -1;
-
-	/* disabled by default */
-	params->deepest_idle_state = -2;
-
-	/* display data in microseconds */
-	params->common.output_divisor = 1000;
-
-	/* default to BPF mode */
-	params->mode = TRACING_MODE_BPF;
-
-	/* default to truncate stack format */
-	params->stack_format = STACK_FORMAT_TRUNCATE;
-
-	while (1) {
-		static struct option long_options[] = {
-			{"auto",		required_argument,	0, 'a'},
-			{"help",		no_argument,		0, 'h'},
-			{"irq",			required_argument,	0, 'i'},
-			{"nano",		no_argument,		0, 'n'},
-			{"period",		required_argument,	0, 'p'},
-			{"quiet",		no_argument,		0, 'q'},
-			{"stack",		required_argument,	0, 's'},
-			{"thread",		required_argument,	0, 'T'},
-			{"trace",		optional_argument,	0, 't'},
-			{"user-threads",	no_argument,		0, 'u'},
-			{"kernel-threads",	no_argument,		0, 'k'},
-			{"user-load",		no_argument,		0, 'U'},
-			{"trigger",		required_argument,	0, '0'},
-			{"filter",		required_argument,	0, '1'},
-			{"dma-latency",		required_argument,	0, '2'},
-			{"no-aa",		no_argument,		0, '3'},
-			{"dump-tasks",		no_argument,		0, '4'},
-			{"aa-only",		required_argument,	0, '5'},
-			{"warm-up",		required_argument,	0, '6'},
-			{"trace-buffer-size",	required_argument,	0, '7'},
-			{"deepest-idle-state",	required_argument,	0, '8'},
-			{"on-threshold",	required_argument,	0, '9'},
-			{"on-end",		required_argument,	0, '\1'},
-			{"bpf-action",		required_argument,	0, '\2'},
-			{"stack-format",	required_argument,	0, '\3'},
-			{0, 0, 0, 0}
-		};
-
-		if (common_parse_options(argc, argv, &params->common))
-			continue;
-
-		c = getopt_auto(argc, argv, long_options);
-
-		/* detect the end of the options. */
-		if (c == -1)
-			break;
-
-		switch (c) {
-		case 'a':
-			auto_thresh = get_llong_from_str(optarg);
-
-			/* set thread stop to auto_thresh */
-			params->common.stop_total_us = auto_thresh;
-			params->common.stop_us = auto_thresh;
-
-			/* get stack trace */
-			params->print_stack = auto_thresh;
-
-			/* set trace */
-			if (!trace_output)
-				trace_output = "timerlat_trace.txt";
-
-			break;
-		case '5':
-			/* it is here because it is similar to -a */
-			auto_thresh = get_llong_from_str(optarg);
-
-			/* set thread stop to auto_thresh */
-			params->common.stop_total_us = auto_thresh;
-			params->common.stop_us = auto_thresh;
-
-			/* get stack trace */
-			params->print_stack = auto_thresh;
-
-			/* set aa_only to avoid parsing the trace */
-			params->common.aa_only = 1;
-			break;
-		case 'h':
-		case '?':
-			timerlat_top_usage();
-			break;
-		case 'i':
-			params->common.stop_us = get_llong_from_str(optarg);
-			break;
-		case 'k':
-			params->common.kernel_workload = true;
-			break;
-		case 'n':
-			params->common.output_divisor = 1;
-			break;
-		case 'p':
-			params->timerlat_period_us = get_llong_from_str(optarg);
-			if (params->timerlat_period_us > 1000000)
-				fatal("Period longer than 1 s");
-			break;
-		case 'q':
-			params->common.quiet = 1;
-			break;
-		case 's':
-			params->print_stack = get_llong_from_str(optarg);
-			break;
-		case 'T':
-			params->common.stop_total_us = get_llong_from_str(optarg);
-			break;
-		case 't':
-			trace_output = parse_optional_arg(argc, argv);
-			if (!trace_output)
-				trace_output = "timerlat_trace.txt";
-			break;
-		case 'u':
-			params->common.user_workload = true;
-			/* fallback: -u implies -U */
-		case 'U':
-			params->common.user_data = true;
-			break;
-		case '0': /* trigger */
-			if (params->common.events)
-				trace_event_add_trigger(params->common.events, optarg);
-			else
-				fatal("--trigger requires a previous -e");
-			break;
-		case '1': /* filter */
-			if (params->common.events)
-				trace_event_add_filter(params->common.events, optarg);
-			else
-				fatal("--filter requires a previous -e");
-			break;
-		case '2': /* dma-latency */
-			params->dma_latency = get_llong_from_str(optarg);
-			if (params->dma_latency < 0 || params->dma_latency > 10000)
-				fatal("--dma-latency needs to be >= 0 and < 10000");
-			break;
-		case '3': /* no-aa */
-			params->no_aa = 1;
-			break;
-		case '4':
-			params->dump_tasks = 1;
-			break;
-		case '6':
-			params->common.warmup = get_llong_from_str(optarg);
-			break;
-		case '7':
-			params->common.buffer_size = get_llong_from_str(optarg);
-			break;
-		case '8':
-			params->deepest_idle_state = get_llong_from_str(optarg);
-			break;
-		case '9':
-			retval = actions_parse(&params->common.threshold_actions, optarg,
-					       "timerlat_trace.txt");
-			if (retval)
-				fatal("Invalid action %s", optarg);
-			break;
-		case '\1':
-			retval = actions_parse(&params->common.end_actions, optarg,
-					       "timerlat_trace.txt");
-			if (retval)
-				fatal("Invalid action %s", optarg);
-			break;
-		case '\2':
-			params->bpf_action_program = optarg;
-			break;
-		case '\3':
-			params->stack_format = parse_stack_format(optarg);
-			if (params->stack_format == -1)
-				fatal("Invalid --stack-format option");
-			break;
-		default:
-			fatal("Invalid option");
-		}
-	}
-
-	if (trace_output)
-		actions_add_trace_output(&params->common.threshold_actions, trace_output);
-
-	if (geteuid())
-		fatal("rtla needs root permission");
-
-	/*
-	 * Auto analysis only happens if stop tracing, thus:
-	 */
-	if (!params->common.stop_us && !params->common.stop_total_us)
-		params->no_aa = 1;
-
-	if (params->no_aa && params->common.aa_only)
-		fatal("--no-aa and --aa-only are mutually exclusive!");
-
-	if (params->common.kernel_workload && params->common.user_workload)
-		fatal("--kernel-threads and --user-threads are mutually exclusive!");
-
-	/*
-	 * If auto-analysis or trace output is enabled, switch from BPF mode to
-	 * mixed mode
-	 */
-	if (params->mode == TRACING_MODE_BPF &&
-	    (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] ||
-	     params->common.end_actions.present[ACTION_TRACE_OUTPUT] ||
-	     !params->no_aa))
-		params->mode = TRACING_MODE_MIXED;
-
-	return &params->common;
-}
-
 /*
  * timerlat_top_apply_config - apply the top configs to the initialized tool
  */
diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c
index 9cec5b3e02c8..cb187e7d48d1 100644
--- a/tools/tracing/rtla/src/utils.c
+++ b/tools/tracing/rtla/src/utils.c
@@ -22,7 +22,7 @@
 #include "common.h"
 
 #define MAX_MSG_LENGTH	1024
-int config_debug;
+bool config_debug;
 
 /*
  * err_msg - print an error message to the stderr
@@ -1011,32 +1011,6 @@ int auto_house_keeping(cpu_set_t *monitored_cpus)
 	return 1;
 }
 
-/**
- * parse_optional_arg - Parse optional argument value
- *
- * Parse optional argument value, which can be in the form of:
- * -sarg, -s/--long=arg, -s/--long arg
- *
- * Returns arg value if found, NULL otherwise.
- */
-char *parse_optional_arg(int argc, char **argv)
-{
-	if (optarg) {
-		if (optarg[0] == '=') {
-			/* skip the = */
-			return &optarg[1];
-		} else {
-			return optarg;
-		}
-	/* parse argument of form -s [arg] and --long [arg]*/
-	} else if (optind < argc && argv[optind][0] != '-') {
-		/* consume optind */
-		return argv[optind++];
-	} else {
-		return NULL;
-	}
-}
-
 /*
  * strtoi - convert string to integer with error checking
  *
diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h
index 96fd72042717..2ba3333669bb 100644
--- a/tools/tracing/rtla/src/utils.h
+++ b/tools/tracing/rtla/src/utils.h
@@ -39,7 +39,7 @@ static inline bool str_has_prefix(const char *str, const char *prefix)
 	return strncmp(str, prefix, strlen(prefix)) == 0;
 }
 
-extern int config_debug;
+extern bool config_debug;
 void debug_msg(const char *fmt, ...);
 void err_msg(const char *fmt, ...);
 void fatal(const char *fmt, ...);
@@ -47,7 +47,6 @@ void fatal(const char *fmt, ...);
 long parse_seconds_duration(char *val);
 void get_duration(time_t start_time, char *output, int output_size);
 
-char *parse_optional_arg(int argc, char **argv);
 long long get_llong_from_str(char *start);
 
 static inline void
diff --git a/tools/tracing/rtla/tests/hwnoise.t b/tools/tracing/rtla/tests/hwnoise.t
index 23ce250a6852..cfe687ff5ee1 100644
--- a/tools/tracing/rtla/tests/hwnoise.t
+++ b/tools/tracing/rtla/tests/hwnoise.t
@@ -6,7 +6,7 @@ test_begin
 set_timeout 2m
 
 check "verify help page" \
-	"hwnoise --help" 0 "summary of hardware-related noise"
+	"hwnoise --help" 129 "Usage: rtla hwnoise"
 check "detect noise higher than one microsecond" \
 	"hwnoise -c 0 -T 1 -d 5s -q" 0
 check "set the automatic trace mode" \
diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t
index 06787471d0e8..c3dce1c9d197 100644
--- a/tools/tracing/rtla/tests/osnoise.t
+++ b/tools/tracing/rtla/tests/osnoise.t
@@ -6,9 +6,9 @@ test_begin
 set_timeout 2m
 
 check "verify help page" \
-	"osnoise --help" 0 "osnoise version"
+	"osnoise --help" 129 "osnoise version"
 check_top_hist "verify help page" \
-	"osnoise TOOL --help" 0 "rtla osnoise"
+	"osnoise TOOL --help" 129 "rtla osnoise"
 check_top_q_hist "verify the --stop/-s param" \
 	"osnoise TOOL -s 30 -T 1" 2 "osnoise hit stop tracing"
 check_top_q_hist "verify the --trace param" \
@@ -32,7 +32,7 @@ check "hist with -b/--bucket-size" \
 check "hist with -E/--entries" \
 	"osnoise hist -E 10 -d 1s"
 check "hist with -E/--entries out of range" \
-	"osnoise hist -E 1 -d 1s" 1 "^Entries must be > 10 and < 9999999$"
+	"osnoise hist -E 1 -d 1s" 1 "^Entries must be > 10 and < 10000000$"
 check "hist with --no-header" \
 	"osnoise hist --no-header -d 1s" 0 "" "RTLA osnoise histogram"
 check "hist with --with-zeros" \
diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t
index 3ebfe316b39e..27fd3143b379 100644
--- a/tools/tracing/rtla/tests/timerlat.t
+++ b/tools/tracing/rtla/tests/timerlat.t
@@ -21,9 +21,9 @@ export RTLA_NO_BPF=$option
 
 # Basic tests
 check "verify help page" \
-	"timerlat --help" 0 "timerlat version"
+	"timerlat --help" 129 "timerlat version"
 check_top_hist "verify help page" \
-	"timerlat TOOL --help" 0 "rtla timerlat"
+	"timerlat TOOL --help" 129 "rtla timerlat"
 check_top_hist "verify -s/--stack" \
 	"timerlat TOOL -s 3 -T 10 -t" 2 "Blocking thread stack trace"
 check_top_hist "test in nanoseconds" \
@@ -61,7 +61,7 @@ check "hist with -b/--bucket-size" \
 check "hist with -E/--entries" \
 	"timerlat hist -E 10 -d 1s"
 check "hist with -E/--entries out of range" \
-	"timerlat hist -E 1 -d 1s" 1 "^Entries must be > 10 and < 9999999$"
+	"timerlat hist -E 1 -d 1s" 1 "^Entries must be > 10 and < 10000000$"
 check "hist with --no-header" \
 	"timerlat hist --no-header -d 1s" 0 "" "RTLA timerlat histogram"
 check "hist with --with-zeros" \
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 3/6] tools subcmd: allow parsing distinct --opt and --no-opt
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
  To: Steven Rostedt, Tomas Glozar
  Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
	Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
	Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
	linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>

libsubcmd automatically generates for every option --opt an equivalent
negated option, --no-opt, to unset the option. Vice versa, for every
option declared as --no-opt, a shorthand --opt is declared for
convenience.

Add a flag, PARSE_OPT_NOAUTONEG, to disable this behavior. This new flag
behaves similarly to the already existing PARSE_OPT_NONEG, only it does
not reject the --no-opt variant, but leaves it undefined. That is useful
when there is a conflicting distinct --no-opt option in the syntax of
the tool.

PARSE_OPT_NOAUTONEG is enabled per-option, allowing to unset other
options that do not have this conflict.

Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
 tools/lib/subcmd/parse-options.c | 10 ++++++----
 tools/lib/subcmd/parse-options.h |  3 +++
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/tools/lib/subcmd/parse-options.c b/tools/lib/subcmd/parse-options.c
index 664b2053bb77..e83200e9f56a 100644
--- a/tools/lib/subcmd/parse-options.c
+++ b/tools/lib/subcmd/parse-options.c
@@ -427,7 +427,8 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
 			return 0;
 		}
 		if (!rest) {
-			if (strstarts(options->long_name, "no-")) {
+			if (strstarts(options->long_name, "no-") &&
+			    !(options->flags & PARSE_OPT_NOAUTONEG)) {
 				/*
 				 * The long name itself starts with "no-", so
 				 * accept the option without "no-" so that users
@@ -465,12 +466,12 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
 				continue;
 			}
 			/* negated and abbreviated very much? */
-			if (strstarts("no-", arg)) {
+			if (strstarts("no-", arg) && !(options->flags & PARSE_OPT_NOAUTONEG)) {
 				flags |= OPT_UNSET;
 				goto is_abbreviated;
 			}
 			/* negated? */
-			if (strncmp(arg, "no-", 3))
+			if (strncmp(arg, "no-", 3) || (options->flags & PARSE_OPT_NOAUTONEG))
 				continue;
 			flags |= OPT_UNSET;
 			rest = skip_prefix(arg + 3, options->long_name);
@@ -1019,7 +1020,8 @@ int parse_options_usage(const char * const *usagestr,
 		if (strstarts(opts->long_name, optstr))
 			print_option_help(opts, 0);
 		if (strstarts("no-", optstr) &&
-		    strstarts(opts->long_name, optstr + 3))
+		    strstarts(opts->long_name, optstr + 3) &&
+			!(opts->flags & PARSE_OPT_NOAUTONEG))
 			print_option_help(opts, 0);
 	}
 
diff --git a/tools/lib/subcmd/parse-options.h b/tools/lib/subcmd/parse-options.h
index c573a0ca5ca6..38df5fd21963 100644
--- a/tools/lib/subcmd/parse-options.h
+++ b/tools/lib/subcmd/parse-options.h
@@ -47,6 +47,7 @@ enum parse_opt_option_flags {
 	PARSE_OPT_NOEMPTY  = 128,
 	PARSE_OPT_NOBUILD  = 256,
 	PARSE_OPT_CANSKIP  = 512,
+	PARSE_OPT_NOAUTONEG = 1024,
 };
 
 struct option;
@@ -149,6 +150,8 @@ struct option {
 	{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = "time", .help = (h), .callback = parse_opt_approxidate_cb }
 #define OPT_CALLBACK(s, l, v, a, h, f) \
 	{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f) }
+#define OPT_CALLBACK_FLAG(s, l, v, a, h, f, fl) \
+	{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f), .flags = (fl) }
 #define OPT_CALLBACK_SET(s, l, v, os, a, h, f) \
 	{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f), .set = check_vtype(os, bool *)}
 #define OPT_CALLBACK_NOOPT(s, l, v, a, h, f) \
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 2/6] tools subcmd: support optarg as separate argument
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
  To: Steven Rostedt, Tomas Glozar
  Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
	Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
	Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
	linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>

In addition to "-ovalue" and "--opt=value" syntax, allow also "-o value"
and "--opt value" for options with optional argument when the newly
added PARSE_OPT_OPTARG_ALLOW_NEXT flag is set.

This behavior is turned off by default since it does not make sense for
tools using non-option command line arguments. Consider the ambiguity
of "cmd -d x", where "-d x" can mean either "-d with argument of x" or
"-d without argument, followed by non-option argument x". This is not an
issue in the case that the tool takes no non-option arguments.

To implement this, a new local variable, force_defval, is created in
get_value(), along with a comment explaining the logic.

Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
 tools/lib/subcmd/parse-options.c | 53 +++++++++++++++++++++++++++-----
 tools/lib/subcmd/parse-options.h |  1 +
 2 files changed, 46 insertions(+), 8 deletions(-)

diff --git a/tools/lib/subcmd/parse-options.c b/tools/lib/subcmd/parse-options.c
index 555d617c1f50..664b2053bb77 100644
--- a/tools/lib/subcmd/parse-options.c
+++ b/tools/lib/subcmd/parse-options.c
@@ -72,6 +72,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 	const char *s, *arg = NULL;
 	const int unset = flags & OPT_UNSET;
 	int err;
+	bool force_defval = false;
 
 	if (unset && p->opt)
 		return opterror(opt, "takes no value", flags);
@@ -123,6 +124,42 @@ static int get_value(struct parse_opt_ctx_t *p,
 		}
 	}
 
+	if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (!(p->flags & PARSE_OPT_OPTARG_ALLOW_NEXT)) {
+			/*
+			 * If the option has an optional argument, and the argument is not
+			 * provided in the option itself, do not attempt to get it from
+			 * the next argument, unless PARSE_OPT_OPTARG_ALLOW_NEXT is set.
+			 *
+			 * This prevents a non-option argument from being interpreted as an
+			 * optional argument of a preceding option, for example:
+			 *
+			 * $ cmd --opt val
+			 * -> is "val" argument of "--opt" or a separate non-option
+			 * argument?
+			 *
+			 * With PARSE_OPT_OPTARG_ALLOW_NEXT, "val" is interpreted as
+			 * the argument of "--opt", i.e. the same as "--opt=val".
+			 * Without PARSE_OPT_OPTARG_ALLOW_NEXT, --opt is interpreted
+			 * as having the default value, and "val" as a separate non-option
+			 * argument.
+			 *
+			 * PARSE_OPT_OPTARG_ALLOW_NEXT is useful for commands that take no
+			 * non-option arguments and want to allow more flexibility in
+			 * optional argument passing.
+			 */
+			force_defval = true;
+		}
+
+		if (p->argc <= 1 || p->argv[1][0] == '-') {
+			/*
+			 * If next argument is an option or does not exist,
+			 * use the default value.
+			 */
+			force_defval = true;
+		}
+	}
+
 	if (opt->flags & PARSE_OPT_NOBUILD) {
 		char reason[128];
 		bool noarg = false;
@@ -148,7 +185,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			noarg = true;
 		if (opt->flags & PARSE_OPT_NOARG)
 			noarg = true;
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+		if (force_defval)
 			noarg = true;
 
 		switch (opt->type) {
@@ -212,7 +249,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 		err = 0;
 		if (unset)
 			*(const char **)opt->value = NULL;
-		else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+		else if (force_defval)
 			*(const char **)opt->value = (const char *)opt->defval;
 		else
 			err = get_arg(p, opt, flags, (const char **)opt->value);
@@ -244,7 +281,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
 		if (opt->flags & PARSE_OPT_NOARG)
 			return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+		if (force_defval)
 			return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
 		if (get_arg(p, opt, flags, &arg))
 			return -1;
@@ -255,7 +292,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			*(int *)opt->value = 0;
 			return 0;
 		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (force_defval) {
 			*(int *)opt->value = opt->defval;
 			return 0;
 		}
@@ -271,7 +308,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			*(unsigned int *)opt->value = 0;
 			return 0;
 		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (force_defval) {
 			*(unsigned int *)opt->value = opt->defval;
 			return 0;
 		}
@@ -289,7 +326,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			*(long *)opt->value = 0;
 			return 0;
 		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (force_defval) {
 			*(long *)opt->value = opt->defval;
 			return 0;
 		}
@@ -305,7 +342,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			*(unsigned long *)opt->value = 0;
 			return 0;
 		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (force_defval) {
 			*(unsigned long *)opt->value = opt->defval;
 			return 0;
 		}
@@ -321,7 +358,7 @@ static int get_value(struct parse_opt_ctx_t *p,
 			*(u64 *)opt->value = 0;
 			return 0;
 		}
-		if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+		if (force_defval) {
 			*(u64 *)opt->value = opt->defval;
 			return 0;
 		}
diff --git a/tools/lib/subcmd/parse-options.h b/tools/lib/subcmd/parse-options.h
index 8e9147358a28..c573a0ca5ca6 100644
--- a/tools/lib/subcmd/parse-options.h
+++ b/tools/lib/subcmd/parse-options.h
@@ -33,6 +33,7 @@ enum parse_opt_flags {
 	PARSE_OPT_KEEP_ARGV0 = 4,
 	PARSE_OPT_KEEP_UNKNOWN = 8,
 	PARSE_OPT_NO_INTERNAL_HELP = 16,
+	PARSE_OPT_OPTARG_ALLOW_NEXT = 32,
 };
 
 enum parse_opt_option_flags {
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 1/6] rtla: Add libsubcmd dependency
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
  To: Steven Rostedt, Tomas Glozar
  Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
	Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
	Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
	linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>

In preparation for migrating RTLA to libsubcmd, build libsubcmd from the
appropriate directory next to the RTLA build proper, and link the
resulting object to RTLA.

libsubcmd uses str_error_r() and strlcpy() at several places. To support
these, also link the respective libraries from tools/lib.

For completeness, also add tools/include to include path. This will
allow other userspace functions and macros shipped with the kernel to be
used in RTLA; perf and bpftool, two other users of libsubcmd, already do
that.

To prevent a name conflict, rename RTLA's run_command() function to
run_tool_command(), and replace RTLA's own container_of implementation
with the one in tools/include/linux/container_of.h.

Assisted-by: Composer:composer-1
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
 tools/tracing/rtla/.gitignore  |  2 ++
 tools/tracing/rtla/Makefile    | 66 ++++++++++++++++++++++++++++++----
 tools/tracing/rtla/src/rtla.c  |  8 ++---
 tools/tracing/rtla/src/utils.h |  6 ++--
 4 files changed, 67 insertions(+), 15 deletions(-)

diff --git a/tools/tracing/rtla/.gitignore b/tools/tracing/rtla/.gitignore
index 231fb8d67f97..c7b4bf1c8ba9 100644
--- a/tools/tracing/rtla/.gitignore
+++ b/tools/tracing/rtla/.gitignore
@@ -10,3 +10,5 @@ custom_filename.txt
 osnoise_irq_noise_hist.txt
 osnoise_trace.txt
 timerlat_trace.txt
+libsubcmd/
+lib/
diff --git a/tools/tracing/rtla/Makefile b/tools/tracing/rtla/Makefile
index 45690ee14544..19eb5581fa23 100644
--- a/tools/tracing/rtla/Makefile
+++ b/tools/tracing/rtla/Makefile
@@ -27,6 +27,30 @@ endif
 RTLA		:= $(OUTPUT)rtla
 RTLA_IN		:= $(RTLA)-in.o
 
+LIBSUBCMD_DIR = $(srctree)/tools/lib/subcmd/
+ifneq ($(OUTPUT),)
+  LIBSUBCMD_OUTPUT = $(abspath $(OUTPUT))/libsubcmd
+else
+  LIBSUBCMD_OUTPUT = $(CURDIR)/libsubcmd
+endif
+LIBSUBCMD = $(LIBSUBCMD_OUTPUT)/libsubcmd.a
+LIBSUBCMD_INCLUDES = $(LIBSUBCMD_OUTPUT)/include
+LIBSUBCMD_MAKEFLAGS = O=$(LIBSUBCMD_OUTPUT) DESTDIR=$(LIBSUBCMD_OUTPUT) prefix= subdir=
+
+TOOLS_INCLUDES = -I$(srctree)/tools/include
+
+ifneq ($(OUTPUT),)
+  LIB_OUTPUT = $(abspath $(OUTPUT))/lib
+else
+  LIB_OUTPUT = $(CURDIR)/lib
+endif
+
+LIB_STRING = $(LIB_OUTPUT)/string.o
+LIB_STRING_SRC = $(srctree)/tools/lib/string.c
+
+LIB_STR_ERROR_R = $(LIB_OUTPUT)/str_error_r.o
+LIB_STR_ERROR_R_SRC = $(srctree)/tools/lib/str_error_r.c
+
 VERSION		:= $(shell sh -c "make -sC ../../.. kernelversion | grep -v make")
 DOCSRC		:= ../../../Documentation/tools/rtla/
 
@@ -66,7 +90,7 @@ ifeq ($(config),1)
   include Makefile.config
 endif
 
-CFLAGS		+= $(INCLUDES) $(LIB_INCLUDES)
+CFLAGS		+= $(INCLUDES) $(LIB_INCLUDES) $(TOOLS_INCLUDES) -I$(LIBSUBCMD_INCLUDES)
 
 export CFLAGS OUTPUT srctree
 
@@ -93,20 +117,48 @@ tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c
 	$(Q)echo "BPF skeleton support is disabled, skipping tests/bpf/bpf_action_map.o"
 endif
 
-$(RTLA): $(RTLA_IN)
-	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(EXTLIBS)
+$(RTLA): $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
+	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
 
-static: $(RTLA_IN)
+static: $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
 	$(eval LDFLAGS += -static)
-	$(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN)  $(EXTLIBS)
+	$(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
 
 rtla.%: fixdep FORCE
 	make -f $(srctree)/tools/build/Makefile.build dir=. $@
 
-$(RTLA_IN): fixdep FORCE src/timerlat.skel.h
+$(RTLA_IN): fixdep FORCE src/timerlat.skel.h $(LIBSUBCMD_INCLUDES)
 	make $(build)=rtla
 
-clean: doc_clean fixdep-clean
+$(LIBSUBCMD_OUTPUT):
+	$(Q)$(MKDIR) -p $@
+
+$(LIBSUBCMD_INCLUDES): $(LIBSUBCMD_OUTPUT)
+	$(Q)$(MAKE) -C $(LIBSUBCMD_DIR) $(LIBSUBCMD_MAKEFLAGS) \
+		install_headers
+
+$(LIBSUBCMD): fixdep $(LIBSUBCMD_OUTPUT)
+	$(Q)$(MAKE) -C $(LIBSUBCMD_DIR) $(LIBSUBCMD_MAKEFLAGS) \
+		$@
+
+$(LIB_OUTPUT):
+	$(Q)$(MKDIR) -p $@
+
+$(LIB_STR_ERROR_R): $(LIB_STR_ERROR_R_SRC) $(LIB_OUTPUT)
+	$(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
+
+$(LIB_STRING): $(LIB_STRING_SRC) $(LIB_OUTPUT)
+	$(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
+
+libsubcmd-clean:
+	$(call QUIET_CLEAN, libsubcmd)
+	$(Q)$(RM) -r -- $(LIBSUBCMD_OUTPUT)
+
+lib-clean:
+	$(call QUIET_CLEAN, lib)
+	$(Q)$(RM) -r -- $(LIB_OUTPUT)
+
+clean: doc_clean fixdep-clean libsubcmd-clean lib-clean
 	$(call QUIET_CLEAN, rtla)
 	$(Q)find . -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete
 	$(Q)rm -f rtla rtla-static fixdep FEATURE-DUMP rtla-*
diff --git a/tools/tracing/rtla/src/rtla.c b/tools/tracing/rtla/src/rtla.c
index 3398250076ea..f4342ca684c3 100644
--- a/tools/tracing/rtla/src/rtla.c
+++ b/tools/tracing/rtla/src/rtla.c
@@ -38,12 +38,12 @@ static void rtla_usage(int err)
 }
 
 /*
- * run_command - try to run a rtla tool command
+ * run_tool_command - try to run a rtla tool command
  *
  * It returns 0 if it fails. The tool's main will generally not
  * return as they should call exit().
  */
-int run_command(int argc, char **argv, int start_position)
+int run_tool_command(int argc, char **argv, int start_position)
 {
 	if (strcmp(argv[start_position], "osnoise") == 0) {
 		osnoise_main(argc-start_position, &argv[start_position]);
@@ -69,7 +69,7 @@ int main(int argc, char *argv[])
 	int retval;
 
 	/* is it an alias? */
-	retval = run_command(argc, argv, 0);
+	retval = run_tool_command(argc, argv, 0);
 	if (retval)
 		exit(0);
 
@@ -82,7 +82,7 @@ int main(int argc, char *argv[])
 		rtla_usage(0);
 	}
 
-	retval = run_command(argc, argv, 1);
+	retval = run_tool_command(argc, argv, 1);
 	if (retval)
 		exit(0);
 
diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h
index e794ede64b2c..96fd72042717 100644
--- a/tools/tracing/rtla/src/utils.h
+++ b/tools/tracing/rtla/src/utils.h
@@ -7,6 +7,8 @@
 #include <stdbool.h>
 #include <stdlib.h>
 
+#include <linux/container_of.h>
+
 /*
  * '18446744073709551615\0'
  */
@@ -37,10 +39,6 @@ static inline bool str_has_prefix(const char *str, const char *prefix)
 	return strncmp(str, prefix, strlen(prefix)) == 0;
 }
 
-#define container_of(ptr, type, member)({			\
-	const typeof(((type *)0)->member) *__mptr = (ptr);	\
-	(type *)((char *)__mptr - offsetof(type, member)) ; })
-
 extern int config_debug;
 void debug_msg(const char *fmt, ...);
 void err_msg(const char *fmt, ...);
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 0/6] rtla: Migrate to libsubcmd for command line option parsing
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
  To: Steven Rostedt, Tomas Glozar
  Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
	Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
	Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
	linux-perf-users

[ CC to linux-perf-users for the libsubcmd code changes ]

rtla currently uses its own implementation that uses getopt_long() to
parse command-line arguments.

Migrate rtla to use libsubcmd for command line argument parsing,
similarly to what is already done by other tools like perf, bpftool,
and objtool. Among other benefits, this allows help messages to be
generated automatically rather than having to be typed out manually
for each tool.

libsubcmd is extended with a flag to parse optarg from separate
argument if a new flag is turned on. Without the flag, the old behavior
is preserved. That keeps the parsing working for tools that use
positional arguments, and allows RTLA to keep its flexible syntax for -C
and -t options and their long variants, --cgroup and --trace-output.
Another flag is added to disable automatic definition of --no-xy for
every option --xy and vice versa, which overlaps for RTLA's --irq and
--thread options.

The new implementation is moved into a separate file, cli.c, together
with a tiny header counterpart, cli.h. This helps separate the parsing
logic, which has little in common with the rest of RTLA, in a separate
module. Another new file, cli_p.h, is used as a private header to contain
macros and static function declarations that are also used by unit tests
next to cli.c, but should not be imported from elsewhere.

Macros to generate struct option array fields for libsubcmd's
parse_args() are used to preserve the consolidation of argument parsing
code across different RTLA tools. Kernel and user threads are, as
an exception, treated as common, although they are currently implemented
for timerlat only, in line with earlier consolidation changes.

The test suite is expanded to include two levels of unit tests, one testing
the already existing tool_parse_args() functions, one tests option callbacks,
which are a new level of the CLI parser added in this patchset. This helps
to verify that no regressions are caused by this refactoring.

I expect more improvements to the code being possible in the future,
like creating macros for option groups to further deduplicate the code,
reducing the amount of extra code in the _parse_args() functions, or
implementing support for unsetting options (which is currently only
supported for those that do not use a custom callback).

v2 changes:
- Two unit test suites are added to cover regressions, after several
options were broken by the v1. The test suites cover all parsing
issues reported in the v1 as well as those found during additional
testing.
- Return value of all paths that print help, including those that
are handled in RTLA, are set to libsubcmd's help exit code of 129.
Previously, only the tool help returned 129. While some other tools
(e.g. bpftool) do that, RTLA unifies those for consistency. The return
value is also added to the corresponding section in documentation.
- Incorrect parsing of --no-irq and --no-thread is fixed using a newly
added libsubcmd option flag.
- Incorrect parsing of -n and -u timerlat options (which erroneously
required an argument in v1) is fixed.
- A now stale declaration of removed function common_parse_options()
is removed from common.h.
- Segmentation fault on abbreviated --help (e.g. --he) is fixed.
- Incorrect formatting of OPT_END macro (spurious tab) is fixed.
- All opt_* callbacks now reject unimplemented unset (--no-) correctly.
- opt_trigger_cb() and opt_filter_cb() now take only the required events
field, not the whole params structure.
- Spurious opt_osnoise_threshold_cb() which is actually just a wrapper for
opt_llong_callback() is removed.
- Old off-by-one typo is fixed in --dma-latency and -E/--entries error
messages, to make it consistent with the newly added unit tests.
- Fix a bug in Makefile that defined LIBSUBCMD_INCLUDES as -I... and then
used it as a target name: define it as the path only, and then add -I$(...)
to CFLAGS, as this is the only include path that is generated during the build
itself.

v1: https://lore.kernel.org/linux-trace-kernel/20260320150651.51057-1-tglozar@redhat.com/T

Tomas Glozar (6):
  rtla: Add libsubcmd dependency
  tools subcmd: support optarg as separate argument
  tools subcmd: allow parsing distinct --opt and --no-opt
  rtla: Parse cmdline using libsubcmd
  rtla/tests: Add unit tests for _parse_args() functions
  rtla/tests: Add unit tests for CLI option callbacks

 Documentation/tools/rtla/common_appendix.txt  |   7 +-
 tools/lib/subcmd/parse-options.c              |  63 +-
 tools/lib/subcmd/parse-options.h              |   4 +
 tools/tracing/rtla/.gitignore                 |   2 +
 tools/tracing/rtla/Makefile                   |  66 +-
 tools/tracing/rtla/src/Build                  |   2 +-
 tools/tracing/rtla/src/cli.c                  | 537 +++++++++++++
 tools/tracing/rtla/src/cli.h                  |   9 +
 tools/tracing/rtla/src/cli_p.h                | 670 +++++++++++++++++
 tools/tracing/rtla/src/common.c               | 109 ---
 tools/tracing/rtla/src/common.h               |  27 +-
 tools/tracing/rtla/src/osnoise.c              |   9 +-
 tools/tracing/rtla/src/osnoise_hist.c         | 221 +-----
 tools/tracing/rtla/src/osnoise_top.c          | 200 +----
 tools/tracing/rtla/src/rtla.c                 |  92 ---
 tools/tracing/rtla/src/timerlat.c             |   9 +-
 tools/tracing/rtla/src/timerlat.h             |   6 +-
 tools/tracing/rtla/src/timerlat_hist.c        | 317 +-------
 tools/tracing/rtla/src/timerlat_top.c         | 286 +------
 tools/tracing/rtla/src/utils.c                |  28 +-
 tools/tracing/rtla/src/utils.h                |   9 +-
 tools/tracing/rtla/tests/hwnoise.t            |   2 +-
 tools/tracing/rtla/tests/osnoise.t            |   6 +-
 tools/tracing/rtla/tests/timerlat.t           |   6 +-
 tools/tracing/rtla/tests/unit/Build           |   5 +
 tools/tracing/rtla/tests/unit/Makefile.unit   |   4 +-
 .../rtla/tests/unit/cli_opt_callback.c        | 704 ++++++++++++++++++
 .../rtla/tests/unit/cli_params_assert.h       |  68 ++
 .../rtla/tests/unit/osnoise_hist_cli.c        | 557 ++++++++++++++
 .../tracing/rtla/tests/unit/osnoise_top_cli.c | 503 +++++++++++++
 .../rtla/tests/unit/timerlat_hist_cli.c       | 702 +++++++++++++++++
 .../rtla/tests/unit/timerlat_top_cli.c        | 634 ++++++++++++++++
 tools/tracing/rtla/tests/unit/unit_tests.c    |  13 +
 33 files changed, 4566 insertions(+), 1311 deletions(-)
 create mode 100644 tools/tracing/rtla/src/cli.c
 create mode 100644 tools/tracing/rtla/src/cli.h
 create mode 100644 tools/tracing/rtla/src/cli_p.h
 delete mode 100644 tools/tracing/rtla/src/rtla.c
 create mode 100644 tools/tracing/rtla/tests/unit/cli_opt_callback.c
 create mode 100644 tools/tracing/rtla/tests/unit/cli_params_assert.h
 create mode 100644 tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
 create mode 100644 tools/tracing/rtla/tests/unit/osnoise_top_cli.c
 create mode 100644 tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
 create mode 100644 tools/tracing/rtla/tests/unit/timerlat_top_cli.c

-- 
2.54.0


^ permalink raw reply

* Re: [PATCH v20 02/10] ring-buffer: Skip invalid sub-buffers when rewinding persistent ring buffer
From: Steven Rostedt @ 2026-05-21 14:17 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel
  Cc: Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Ian Rogers, sashiko-bot@kernel.org,
	sashiko-reviews@lists.linux.dev
In-Reply-To: <20260520185017.219342323@kernel.org>


[
  Replying to Sashiko from v19: https://sashiko.dev/?list=org.kernel.vger.linux-trace-kernel#/patchset/177751968499.2136606.17388366710182662849.stgit%40mhiramat.tok.corp.google.com
]

> > @@ -1892,9 +1895,27 @@ static int rb_validate_buffer(struct buffer_data_page *dpage, int cpu,
> >  	 * subbuf_size is considered invalid.
> >  	 */
> >  	tail = local_read(&dpage->commit) & ~RB_MISSED_MASK;
> > -	if (tail > meta->subbuf_size - BUF_PAGE_HDR_SIZE)
> > -		return -1;
> > -	return rb_read_data_buffer(dpage, tail, cpu, &ts, &delta);
> > +	if (tail <= meta->subbuf_size - BUF_PAGE_HDR_SIZE)
> > +		ret = rb_read_data_buffer(dpage, tail, cpu, &ts, &delta);

> Since tail is read from untrusted persistent memory, does this call evaluating

The persistent memory is not untrusted.

> event headers verify enough bytes remain in the sub-buffer?
> If tail is set to the maximum allowed value, and preceding events are crafted
> so that the last event starts at tail - 1, the event pointer inside
> rb_read_data_buffer() will point to the very last byte.
> Calling rb_event_length(event) will evaluate event->type_len via a 32-bit
> read which could read past the page boundary and cause a regression if the
> adjacent page is unmapped.
> Should the parsing loop verify tail - e >= sizeof(struct ring_buffer_event)
> before evaluating the event?

> > +	else
> > +		ret = -1;
> > +
> > +	/*
> > +	 * The timestamp must be greater than @prev_ts and smaller than @next_ts.
> > +	 * Since this function works in both forward (verify) and reverse (unwind)
> > +	 * loop, we don't know both @prev_ts and @next_ts at the same time.
> > +	 * So use the known boundary as the boundary.
> > +	 */
> > +	if (ret < 0 || (prev_ts && prev_ts > ts) || (next_ts && ts > next_ts)) {

> Does this timestamp validation properly enforce strictly monotonic page ordering?
> Inside rb_validate_buffer(), ts is updated by rb_read_data_buffer() to
> the timestamp of the last event in the current page. The condition
> (prev_ts && prev_ts > ts) only checks that the previous page's start time
> is not greater than the current page's end time.
> Could a corrupted page with a start timestamp earlier than the previous
> page incorrectly pass validation if its final event timestamp is large enough?
> Would it prevent this regression to enforce that the current page starts after
> the previous page by checking prev_ts > dpage->time_stamp?

This may be an enhancement but shouldn't be critical for what is happening now.

> > +		local_set(&bpage->entries, 0);
> [ ... ]
> > @@ -1915,25 +1936,29 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer)
> >  	orig_head = head_page = cpu_buffer->head_page;
> >  	orig_reader = cpu_buffer->reader_page;
> >  
> > -	/* Do the reader page first */
> > -	ret = rb_validate_buffer(orig_reader->page, cpu_buffer->cpu, meta);
> > +	/* Do the head page first */
> > +	ret = rb_validate_buffer(head_page, cpu_buffer->cpu, meta, 0, 0);
> > +	if (ret < 0) {
> > +		pr_info("Ring buffer meta [%d] invalid head page detected\n",
> > +			cpu_buffer->cpu);
> > +		goto skip_rewind;
> > +	}
> > +	ts = head_page->page->time_stamp;
> > +
> > +	/* Do the reader page - reader must be previous to head. */
> > +	ret = rb_validate_buffer(orig_reader, cpu_buffer->cpu, meta, 0, ts);

> If rb_validate_buffer() for the head page fails, we take the goto skip_rewind
> path. Since skip_rewind jumps past this orig_reader validation, and the
> iteration loop explicitly skips orig_reader:
>     /* Iterate until finding the commit page */
>     for (i = 0; i < meta->nr_subbufs + 1; i++, rb_inc_page(&head_page)) {
>         /* The original reader page has already been checked/counted. */
>         if (head_page == orig_reader)
>             continue;
> does this mean the reader page escapes validation entirely, introducing a
> regression?

This is a bug and needs to be fixed. If the head page is invalid it
shouldn't skip the rewind but instead jump to the "invalid:" label.

> If an attacker provides a corrupted persistent ring buffer with an invalid
> head page, would the kernel blindly trust the unvalidated commit length
> and event data of the reader page?

Not worried about attackers but I am worried about corrupted memory.

-- Steve

^ permalink raw reply

* [PATCH] tracing: Move trace_iterator_increment() into trace_find_next_entry_inc()
From: Steven Rostedt @ 2026-05-21 13:50 UTC (permalink / raw)
  To: LKML, Linux Trace Kernel; +Cc: Masami Hiramatsu, Mathieu Desnoyers

From: Steven Rostedt <rostedt@goodmis.org>

trace_iterator_increment() is only called from trace_find_next_entry_inc().
It's a small enough function that really doesn't need to be separated.

Move the code from trace_iterator_increment() into
trace_find_next_entry_inc() and remove trace_iterator_increment().

Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
---
 kernel/trace/trace.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 4573f65d68ce..0b43e88ac378 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -2338,15 +2338,6 @@ void trace_last_func_repeats(struct trace_array *tr,
 	__buffer_unlock_commit(buffer, event);
 }
 
-static void trace_iterator_increment(struct trace_iterator *iter)
-{
-	struct ring_buffer_iter *buf_iter = trace_buffer_iter(iter, iter->cpu);
-
-	iter->idx++;
-	if (buf_iter)
-		ring_buffer_iter_advance(buf_iter);
-}
-
 static struct trace_entry *
 peek_next_entry(struct trace_iterator *iter, int cpu, u64 *ts,
 		unsigned long *lost_events)
@@ -2676,11 +2667,17 @@ struct trace_entry *trace_find_next_entry(struct trace_iterator *iter,
 /* Find the next real entry, and increment the iterator to the next entry */
 void *trace_find_next_entry_inc(struct trace_iterator *iter)
 {
+	struct ring_buffer_iter *buf_iter;
+
 	iter->ent = __find_next_entry(iter, &iter->cpu,
 				      &iter->lost_events, &iter->ts);
 
-	if (iter->ent)
-		trace_iterator_increment(iter);
+	if (iter->ent) {
+		iter->idx++;
+		buf_iter = trace_buffer_iter(iter, iter->cpu);
+		if (buf_iter)
+			ring_buffer_iter_advance(buf_iter);
+	}
 
 	return iter->ent ? iter : NULL;
 }
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH v6 05/43] KVM: guest_memfd: Wire up kvm_get_memory_attributes() to per-gmem attributes
From: Fuad Tabba @ 2026-05-21 13:48 UTC (permalink / raw)
  To: Sean Christopherson
  Cc: Ackerley Tng, aik, andrew.jones, binbin.wu, brauner, chao.p.peng,
	david, ira.weiny, jmattson, jthoughton, michael.roth, oupton,
	pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
	steven.price, willy, wyihan, yan.y.zhao, forkloop, pratyush,
	suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, Shuah Khan,
	Vishal Annapurve, Andrew Morton, Chris Li, Kairui Song,
	Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Axel Rasmussen,
	Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng, Shakeel Butt,
	Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka, kvm,
	linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
	linux-mm, linux-coco
In-Reply-To: <ag8JIlHjohAOC3-g@google.com>

On Thu, 21 May 2026 at 14:31, Sean Christopherson <seanjc@google.com> wrote:
>
> On Thu, May 21, 2026, Fuad Tabba wrote:
> > On Wed, 20 May 2026 at 22:44, Ackerley Tng <ackerleytng@google.com> wrote:
> > >
> > > Fuad Tabba <tabba@google.com> writes:
> > >
> > > >
> > > > [...snip...]
> > > >
> > > >> +unsigned long kvm_gmem_get_memory_attributes(struct kvm *kvm, gfn_t gfn)
> > > >> +{
> > > >> +       struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
> > > >> +       struct inode *inode;
> > > >> +
> > > >> +       /*
> > > >> +        * If this gfn has no associated memslot, there's no chance of the gfn
> > > >> +        * being backed by private memory, since guest_memfd must be used for
> > > >> +        * private memory, and guest_memfd must be associated with some memslot.
> > > >> +        */
> > > >> +       if (!slot)
> > > >> +               return 0;
> > > >> +
> > > >> +       CLASS(gmem_get_file, file)(slot);
> > > >> +       if (!file)
> > > >> +               return 0;
> > > >> +
> > > >> +       inode = file_inode(file);
> > > >> +
> > > >> +       /*
> > > >> +        * Rely on the maple tree's internal RCU lock to ensure a
> > > >> +        * stable result. This result can become stale as soon as the
> > > >> +        * lock is dropped, so the caller _must_ still protect
> > > >> +        * consumption of private vs. shared by checking
> > > >> +        * mmu_invalidate_retry_gfn() under mmu_lock to serialize
> > > >> +        * against ongoing attribute updates.
> > > >> +        */
> > > >> +       return kvm_gmem_get_attributes(inode, kvm_gmem_get_index(slot, gfn));
> > > >> +}
> > > >
> > > > Doesn't this imply that all consumers of kvm_mem_is_private() should
> > > > validate the result using mmu_lock and the invalidation sequence?
> > >
> > > Let me know how I can improve the comment.
> >
> > Given Sean's context, the comment is good I think. I would quibble
> > with the the "_must_ still protect" phrasing being a bit too strict.
> >
> > Maybe just soften it slightly to acknowledge the exception? Something like:
> >
> >   * lock is dropped, so callers that require a strict result _must_ protect
> >   * consumption of private vs. shared by checking mmu_invalidate_retry_gfn()
> >   * under mmu_lock to serialize against ongoing attribute updates. Callers
> >   * doing lockless reads must be able to tolerate a stale result.
> >
> > That aligns the comment with how KVM is actually using it today. That
> > said, this is nitpicking. Feel free to use or ignore.
>
> Hmm, I wonder if we can figure out a way to consolidate some documentation,
> because this is _exactly_ the same pattern that x86's host_pfn_mapping_level()
> deals with (see its big comment below).
>
> There's also the stale comment in kvm_invalidate_memslot(), which, stating the
> obvious, speaks to the memslot+SRCU side of things.
>
> Maybe it makes sense to to find a central location for one giant comment about
> how how MMU notifier events and memslot+SRCU protections work?  And then refer
> to that in paths where some asset needs to be tied into MMU notifiers and/or
> memslots+SRCU?
>
> [*] https://lore.kernel.org/all/agcbWe8s9lmPuJwG@google.com

This would fix a few related issues at once. sgtm
/fuad


/fuad

>
> /*
>  * Lookup the mapping level for @gfn in the current mm.
>  *
>  * WARNING!  Use of host_pfn_mapping_level() requires the caller and the end
>  * consumer to be tied into KVM's handlers for MMU notifier events!
>  *
>  * There are several ways to safely use this helper:
>  *
>  * - Check mmu_invalidate_retry_gfn() after grabbing the mapping level, before
>  *   consuming it.  In this case, mmu_lock doesn't need to be held during the
>  *   lookup, but it does need to be held while checking the MMU notifier.
>  *
>  * - Hold mmu_lock AND ensure there is no in-progress MMU notifier invalidation
>  *   event for the hva.  This can be done by explicit checking the MMU notifier
>  *   or by ensuring that KVM already has a valid mapping that covers the hva.
>  *
>  * - Do not use the result to install new mappings, e.g. use the host mapping
>  *   level only to decide whether or not to zap an entry.  In this case, it's
>  *   not required to hold mmu_lock (though it's highly likely the caller will
>  *   want to hold mmu_lock anyways, e.g. to modify SPTEs).
>  *
>  * Note!  The lookup can still race with modifications to host page tables, but
>  * the above "rules" ensure KVM will not _consume_ the result of the walk if a
>  * race with the primary MMU occurs.
>  */

^ permalink raw reply

* Re: [PATCHv3 04/12] uprobes/x86: Move optimized uprobe from nop5 to nop10
From: Peter Zijlstra @ 2026-05-21 13:35 UTC (permalink / raw)
  To: Jiri Olsa
  Cc: Oleg Nesterov, Ingo Molnar, Masami Hiramatsu, Andrii Nakryiko,
	bpf, linux-trace-kernel
In-Reply-To: <20260521124411.31133-5-jolsa@kernel.org>

On Thu, May 21, 2026 at 02:44:03PM +0200, Jiri Olsa wrote:
> Andrii reported an issue with optimized uprobes [1] that can clobber
> redzone area with call instruction storing return address on stack
> where user code may keep temporary data without adjusting rsp.
> 
> Fixing this by moving the optimized uprobes on top of 10-bytes nop
> instruction, so we can squeeze another instruction to escape the
> redzone area before doing the call, like:
> 
>   lea -0x80(%rsp), %rsp
>   call tramp
> 
> Note the lea instruction is used to adjust the rsp register without
> changing the flags.
> 
> We use nop10 and following transofrmation to optimized instructions
> above and back as suggested by Peterz [2].
> 
> Optimize path (int3_update_optimize):
> 
>   1) Initial state after set_swbp() installed the uprobe:
>       cc 2e 0f 1f 84 00 00 00 00 00
> 
>      From offset 0 this is INT3 followed by the tail of the original
>      10-byte NOP.
> 
>   2) Trap the call slot before rewriting the NOP tail:
>       cc 2e 0f 1f 84 [cc] 00 00 00 00
> 
>      From offset 0 this traps on the uprobe INT3.  A thread reaching
>      offset 5 traps on the temporary INT3 instead of seeing a partially
>      patched call.
> 
>   3) Rewrite the LEA tail and call displacement, keeping both INT3 bytes:
>       cc [8d 64 24 80] cc [d0 d1 d2 d3]
> 
>      From offset 0 and offset 5 this still traps.  The bytes between
>      them are not executable entry points while both traps are in place.
> 
>   4) Restore the call opcode at offset 5:
>       cc 8d 64 24 80 [e8] d0 d1 d2 d3
> 
>      From offset 0 this still traps.  From offset 5 the instruction is
>      the final CALL to the uprobe trampoline.
> 
>   5) Publish the first LEA byte:
>       [48] 8d 64 24 80 e8 d0 d1 d2 d3
> 
>      From offset 0 this is:
>         lea -0x80(%rsp), %rsp
>         call <uprobe-trampoline>
> 
> Unoptimize path (int3_update_unoptimize):
> 
>   1) Initial optimized state:
>       48 8d 64 24 80 e8 d0 d1 d2 d3
>      Same as 5) above.
> 
>   2) Trap new entries before restoring the NOP bytes:
>       [cc] 8d 64 24 80 e8 d0 d1 d2 d3
> 
>      From offset 0 this traps. A thread that had already executed the
>      LEA can still reach the intact CALL at offset 5.
> 
>   3) Restore bytes 1..4 of the original NOP while keeping byte 0 trapped
>      and byte 5 as CALL.
>       cc [2e 0f 1f 84] e8 d0 d1 d2 d3
> 
>      From offset 0 this still traps. Offset 5 is still the CALL for any
>      thread that was already past the first LEA byte.
> 
>   4) Publish the first byte of the original NOP:
>       [66] 2e 0f 1f 84 e8 d0 d1 d2 d3
> 
>      From offset 0 this is the restored 10-byte NOP; the CALL opcode and
>      displacement are now only NOP operands.  Offset 5 still decodes as
>      CALL for a thread that was already there.
> 
> Note as explained in [2] we need to use following nop10:
>        PF1   PF2   ESC   NOPL  MOD   SIB   DISP32
> NOP10: 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 -- cs nopw 0x00000000(%rax,%rax,1)
> 
> which means we need to allow 0x2e prefix which maps to INAT_PFX_CS
> attribute in is_prefix_bad function.
> 
> The optimized uprobe performance stays the same:
> 
>         uprobe-nop     :    3.129 ± 0.013M/s
>         uprobe-push    :    3.045 ± 0.006M/s
>         uprobe-ret     :    1.095 ± 0.004M/s
>   -->   uprobe-nop10   :    7.170 ± 0.020M/s
>         uretprobe-nop  :    2.143 ± 0.021M/s
>         uretprobe-push :    2.090 ± 0.000M/s
>         uretprobe-ret  :    0.942 ± 0.000M/s
>   -->   uretprobe-nop10:    3.381 ± 0.003M/s
>         usdt-nop       :    3.245 ± 0.004M/s
>   -->   usdt-nop10     :    7.256 ± 0.023M/s
> 

> @@ -893,48 +918,134 @@ static int verify_insn(struct page *page, unsigned long vaddr, uprobe_opcode_t *
>  }
>  
>  /*
> + * Modify the optimized instruction by using INT3 breakpoints on SMP.
>   * We completely avoid using stop_machine() here, and achieve the
>   * synchronization using INT3 breakpoints and SMP cross-calls.
>   * (borrowed comment from smp_text_poke_batch_finish)
>   *
> + * The way it is done for optimization (int3_update_optimize):
> + *   1) Start with the uprobe INT3 trap already installed
> + *   2) Add an INT3 trap to the call slot
> + *   3) Update everything but the first byte and the call opcode
> + *   4) Replace the call slot INT3 by the call opcode
> + *   5) Replace the first INT3 by the first byte of the LEA instruction
> + *
> + * The way it is done for unoptimization (int3_update_unoptimize):
> + *   1) Start with the optimized uprobe lea/call instructions
> + *   2) Add an INT3 trap to the address that will be patched
> + *   3) Restore the NOP bytes before the call opcode
> + *   4) Replace the first INT3 by the first byte of the NOP instruction
> + *
> + * Note that unoptimization deliberately keeps the call opcode and displacement
> + * in bytes 5..9. Those bytes become operands of the restored 10-byte NOP.
>   */

One important thing to note is that (as earlier noted by Andrii) the
CALL address is never changed. A new optimization pass will not change
the CALL instruction again.

If you noted this anywhere, I failed to find it. This is crucially
important for the correctness of the scheme and should not be emitted.

That is, please add something like:

  "Since there is only a single uprobe-trampoline, the CALL instruction
  will not be changed across unoptimization/optimization cycles.
  Therefore, any task that is preempted at the CALL instruction is
  guaranteed to observe that CALL and not anything else."



^ permalink raw reply

* Re: [PATCH v6 05/43] KVM: guest_memfd: Wire up kvm_get_memory_attributes() to per-gmem attributes
From: Sean Christopherson @ 2026-05-21 13:31 UTC (permalink / raw)
  To: Fuad Tabba
  Cc: Ackerley Tng, aik, andrew.jones, binbin.wu, brauner, chao.p.peng,
	david, ira.weiny, jmattson, jthoughton, michael.roth, oupton,
	pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
	steven.price, willy, wyihan, yan.y.zhao, forkloop, pratyush,
	suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, Shuah Khan,
	Vishal Annapurve, Andrew Morton, Chris Li, Kairui Song,
	Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Axel Rasmussen,
	Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng, Shakeel Butt,
	Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka, kvm,
	linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
	linux-mm, linux-coco
In-Reply-To: <CA+EHjTzLCD-dU-euZKgzwyEr2ecPqFDNutcaHm2fCDGA+MHVXA@mail.gmail.com>

On Thu, May 21, 2026, Fuad Tabba wrote:
> On Wed, 20 May 2026 at 22:44, Ackerley Tng <ackerleytng@google.com> wrote:
> >
> > Fuad Tabba <tabba@google.com> writes:
> >
> > >
> > > [...snip...]
> > >
> > >> +unsigned long kvm_gmem_get_memory_attributes(struct kvm *kvm, gfn_t gfn)
> > >> +{
> > >> +       struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
> > >> +       struct inode *inode;
> > >> +
> > >> +       /*
> > >> +        * If this gfn has no associated memslot, there's no chance of the gfn
> > >> +        * being backed by private memory, since guest_memfd must be used for
> > >> +        * private memory, and guest_memfd must be associated with some memslot.
> > >> +        */
> > >> +       if (!slot)
> > >> +               return 0;
> > >> +
> > >> +       CLASS(gmem_get_file, file)(slot);
> > >> +       if (!file)
> > >> +               return 0;
> > >> +
> > >> +       inode = file_inode(file);
> > >> +
> > >> +       /*
> > >> +        * Rely on the maple tree's internal RCU lock to ensure a
> > >> +        * stable result. This result can become stale as soon as the
> > >> +        * lock is dropped, so the caller _must_ still protect
> > >> +        * consumption of private vs. shared by checking
> > >> +        * mmu_invalidate_retry_gfn() under mmu_lock to serialize
> > >> +        * against ongoing attribute updates.
> > >> +        */
> > >> +       return kvm_gmem_get_attributes(inode, kvm_gmem_get_index(slot, gfn));
> > >> +}
> > >
> > > Doesn't this imply that all consumers of kvm_mem_is_private() should
> > > validate the result using mmu_lock and the invalidation sequence?
> >
> > Let me know how I can improve the comment.
> 
> Given Sean's context, the comment is good I think. I would quibble
> with the the "_must_ still protect" phrasing being a bit too strict.
> 
> Maybe just soften it slightly to acknowledge the exception? Something like:
> 
>   * lock is dropped, so callers that require a strict result _must_ protect
>   * consumption of private vs. shared by checking mmu_invalidate_retry_gfn()
>   * under mmu_lock to serialize against ongoing attribute updates. Callers
>   * doing lockless reads must be able to tolerate a stale result.
> 
> That aligns the comment with how KVM is actually using it today. That
> said, this is nitpicking. Feel free to use or ignore.

Hmm, I wonder if we can figure out a way to consolidate some documentation,
because this is _exactly_ the same pattern that x86's host_pfn_mapping_level()
deals with (see its big comment below).

There's also the stale comment in kvm_invalidate_memslot(), which, stating the
obvious, speaks to the memslot+SRCU side of things.

Maybe it makes sense to to find a central location for one giant comment about
how how MMU notifier events and memslot+SRCU protections work?  And then refer
to that in paths where some asset needs to be tied into MMU notifiers and/or
memslots+SRCU?

[*] https://lore.kernel.org/all/agcbWe8s9lmPuJwG@google.com


/*
 * Lookup the mapping level for @gfn in the current mm.
 *
 * WARNING!  Use of host_pfn_mapping_level() requires the caller and the end
 * consumer to be tied into KVM's handlers for MMU notifier events!
 *
 * There are several ways to safely use this helper:
 *
 * - Check mmu_invalidate_retry_gfn() after grabbing the mapping level, before
 *   consuming it.  In this case, mmu_lock doesn't need to be held during the
 *   lookup, but it does need to be held while checking the MMU notifier.
 *
 * - Hold mmu_lock AND ensure there is no in-progress MMU notifier invalidation
 *   event for the hva.  This can be done by explicit checking the MMU notifier
 *   or by ensuring that KVM already has a valid mapping that covers the hva.
 *
 * - Do not use the result to install new mappings, e.g. use the host mapping
 *   level only to decide whether or not to zap an entry.  In this case, it's
 *   not required to hold mmu_lock (though it's highly likely the caller will
 *   want to hold mmu_lock anyways, e.g. to modify SPTEs).
 *
 * Note!  The lookup can still race with modifications to host page tables, but
 * the above "rules" ensure KVM will not _consume_ the result of the walk if a
 * race with the primary MMU occurs.
 */

^ permalink raw reply

* Re: [PATCH v6 16/43] KVM: guest_memfd: Use actual size for invalidation in kvm_gmem_release()
From: Fuad Tabba @ 2026-05-21 13:29 UTC (permalink / raw)
  To: Sean Christopherson
  Cc: ackerleytng, aik, andrew.jones, binbin.wu, brauner, chao.p.peng,
	david, ira.weiny, jmattson, jthoughton, michael.roth, oupton,
	pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
	steven.price, willy, wyihan, yan.y.zhao, forkloop, pratyush,
	suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, Shuah Khan,
	Vishal Annapurve, Andrew Morton, Chris Li, Kairui Song,
	Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Axel Rasmussen,
	Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng, Shakeel Butt,
	Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka, kvm,
	linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
	linux-mm, linux-coco
In-Reply-To: <ag8BmtzxTlcuA_zy@google.com>

On Thu, 21 May 2026 at 13:59, Sean Christopherson <seanjc@google.com> wrote:
>
> On Thu, May 21, 2026, Fuad Tabba wrote:
> > Hi Ackerley,
> >
> > On Thu, 7 May 2026 at 21:22, Ackerley Tng via B4 Relay
> > <devnull+ackerleytng.google.com@kernel.org> wrote:
> > >
> > > From: Ackerley Tng <ackerleytng@google.com>
> > >
> > > __kvm_gmem_invalidate_begin() and __kvm_gmem_invalidate_end() actually do
> > > not specially handle -1ul. -1ul is used as a huge number, which legal
> > > indices do not exceed, and hence the invalidation works as expected.
> > >
> > > Since a later patch is going to make use of the exact range, calculate the
> > > size of the guest_memfd inode and use it as the end range for invalidating
> > > SPTEs.
> > >
> > > Signed-off-by: Ackerley Tng <ackerleytng@google.com>
> >
> > Want to look at what Sashiko has to say? Seems to be a real issue:
> >
> > https://sashiko.dev/#/patchset/20260507-gmem-inplace-conversion-v6-0-91ab5a8b19a4%40google.com?part=16
> >
> > If I understand correctly, the fix should simple: use
> > check_add_overflow() to validate the offset and size parameters in
> > kvm_gmem_bind()
> >
> >    int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
> >              unsigned int fd, loff_t offset)
> >    {
> >        loff_t size = slot->npages << PAGE_SHIFT;
> >    +    loff_t end;
> >        unsigned long start, end_index;
> >        struct gmem_file *f;
> > ...
> >    -    if (offset < 0 || !PAGE_ALIGNED(offset) ||
> >    -        offset + size > i_size_read(inode))
> >    +    if (offset < 0 || !PAGE_ALIGNED(offset) ||
> >    +        check_add_overflow(offset, size, &end) ||
>
> Eww, TIL I'm not a fan of check_add_overflow().  Burying an out-param in an
> if-statement is nasty.
>
> >    +        end > i_size_read(inode))
>
> This is all rather silly.  @offset and and @slot->npages are fundamentally
> unsigned values.   I don't see any reason to convert them to signed values, only
> to convert them *back* to unsigned values (when stored in start/end, because xarrays
> operate on "unsigned long" indices).
>
> i_size_read() obviously has to return a positive value, so can't we just do this?

lgtm,
/fuad

>
> diff --git virt/kvm/guest_memfd.c virt/kvm/guest_memfd.c
> index a35a55571a2d..9c6dbb54e800 100644
> --- virt/kvm/guest_memfd.c
> +++ virt/kvm/guest_memfd.c
> @@ -640,9 +640,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
>  }
>
>  int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
> -                 unsigned int fd, loff_t offset)
> +                 unsigned int fd, u64 offset)
>  {
> -       loff_t size = slot->npages << PAGE_SHIFT;
> +       u64 size = slot->npages << PAGE_SHIFT;
>         unsigned long start, end;
>         struct gmem_file *f;
>         struct inode *inode;
> @@ -664,8 +664,7 @@ int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
>
>         inode = file_inode(file);
>
> -       if (offset < 0 || !PAGE_ALIGNED(offset) ||
> -           offset + size > i_size_read(inode))
> +       if (!PAGE_ALIGNED(offset) || offset + size > i_size_read(inode))
>                 goto err;
>
>         filemap_invalidate_lock(inode->i_mapping);
> diff --git virt/kvm/kvm_mm.h virt/kvm/kvm_mm.h
> index 9fcc5d5b7f8d..3cb5ef86d0d9 100644
> --- virt/kvm/kvm_mm.h
> +++ virt/kvm/kvm_mm.h
> @@ -72,7 +72,7 @@ int kvm_gmem_init(struct module *module);
>  void kvm_gmem_exit(void);
>  int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args);
>  int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
> -                 unsigned int fd, loff_t offset);
> +                 unsigned int fd, u64 offset);
>  void kvm_gmem_unbind(struct kvm_memory_slot *slot);
>  #else
>  static inline int kvm_gmem_init(struct module *module)
> @@ -80,9 +80,8 @@ static inline int kvm_gmem_init(struct module *module)
>         return 0;
>  }
>  static inline void kvm_gmem_exit(void) {};
> -static inline int kvm_gmem_bind(struct kvm *kvm,
> -                                        struct kvm_memory_slot *slot,
> -                                        unsigned int fd, loff_t offset)
> +static inline int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
> +                               unsigned int fd, u64 offset)
>  {
>         WARN_ON_ONCE(1);
>         return -EIO;
>

^ permalink raw reply

* Re: [PATCHv3 02/12] uprobes/x86: Remove struct uprobe_trampoline object
From: bot+bpf-ci @ 2026-05-21 13:26 UTC (permalink / raw)
  To: jolsa, oleg, peterz, mingo, mhiramat, andrii
  Cc: bpf, linux-trace-kernel, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260521124411.31133-3-jolsa@kernel.org>

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

> commit 35d93b034b144060a547b507cfb4268692c328c2
> Author: Jiri Olsa <jolsa@kernel.org>
> Date:   Thu May 21 14:44:01 2026 +0200
>
>     uprobes/x86: Remove struct uprobe_trampoline object
>
>     Removing struct uprobe_trampoline object and it's tracking code,
>     because it's not needed. We can do same thing directly on top of
>     struct vm_area_struct objects.
>
>     This makes the code simpler and allows easy propagation of the
>     trampoline vma object into child process in following change.
>
>     Note the original code called destroy_uprobe_trampoline if the
>     optimiation failed, but it only freed the struct uprobe_trampoline
>     object, not the vma.
>
>     Signed-off-by: Jiri Olsa <jolsa@kernel.org>

This looks like a bug fix for a VMA resource leak. The commit message
notes that when uprobe optimization failed, the original code only freed
the struct uprobe_trampoline object but leaked the VMA created by
_install_special_mapping. Should this include:

  Fixes: ba2bfc97b462 ("uprobes/x86: Add support to optimize uprobes")


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26227520868

^ permalink raw reply

* Re: [PATCHv3 11/12] selftests/bpf: Add tests for uprobe nop10 red zone clobbering
From: bot+bpf-ci @ 2026-05-21 13:26 UTC (permalink / raw)
  To: jolsa, oleg, peterz, mingo, mhiramat, andrii
  Cc: bpf, linux-trace-kernel, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260521124411.31133-12-jolsa@kernel.org>

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

> diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> index 969f4deba9fd..efff0c515184 100644
> --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c

[ ... ]

> diff --git a/tools/testing/selftests/bpf/prog_tests/usdt.c b/tools/testing/selftests/bpf/prog_tests/usdt.c
> index fda3a298ccfc..8004c9568ffa 100644
> --- a/tools/testing/selftests/bpf/prog_tests/usdt.c
> +++ b/tools/testing/selftests/bpf/prog_tests/usdt.c

[ ... ]

> diff --git a/tools/testing/selftests/bpf/progs/test_usdt.c b/tools/testing/selftests/bpf/progs/test_usdt.c
> index f00cb52874e0..0ee78fb050a1 100644
> --- a/tools/testing/selftests/bpf/progs/test_usdt.c
> +++ b/tools/testing/selftests/bpf/progs/test_usdt.c

[ ... ]

> diff --git a/tools/testing/selftests/bpf/usdt_2.c b/tools/testing/selftests/bpf/usdt_2.c
> index b359b389f6c0..5e38f8605b02 100644
> --- a/tools/testing/selftests/bpf/usdt_2.c
> +++ b/tools/testing/selftests/bpf/usdt_2.c
> @@ -13,4 +13,17 @@ void usdt_2(void)
>  	USDT(optimized_attach, usdt_2);
>  }
>
> +static volatile unsigned long usdt_red_zone_arg1 = 0xDEADBEEF;
> +static volatile unsigned long usdt_red_zone_arg2 = 0xCAFEBABE;
> +static volatile unsigned long usdt_red_zone_arg3 = 0xFEEDFACE;
> +
> +void __attribute__((noinline)) usdt_red_zone_trigger(void)
> +{
> +	unsigned long a1 = usdt_red_zone_arg1;
> +	unsigned long a2 = usdt_red_zone_arg2;
> +	unsigned long a3 = usdt_red_zone_arg3;
> +
> +	USDT(optimized_attach, usdt_red_zone, a1, a2, a3);
> +}

Following up on a review comment from sashiko-bot: is this test reliable
across different compiler versions?

The x86-64 USDT macro uses the "nor" inline assembly constraint, which allows
the compiler to allocate these operands to CPU registers. Because there is no
register pressure in usdt_red_zone_trigger(), modern compilers might allocate
the local variables a1, a2, and a3 directly to registers rather than the
stack's red zone.

If the operands are placed in registers, the uprobe CALL optimization
clobbering [rsp-8] will not corrupt them. This could cause the test to pass
even on buggy kernels, creating a false positive.

The author acknowledged this concern but kept the test as-is, noting that
selftests compile with -O0 and there's also an assembly-based test.

Would it be safer to explicitly force these operands into the red zone using
inline assembly constraints, rather than depending on the compiler's register
allocator?

Reference:
https://lore.kernel.org/bpf/20260509021238.AE98FC2BCB0@smtp.kernel.org/


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26227520868

^ permalink raw reply

* Re: [PATCHv3 08/12] selftests/bpf: Change uprobe syscall tests to use nop10
From: bot+bpf-ci @ 2026-05-21 13:26 UTC (permalink / raw)
  To: jolsa, oleg, peterz, mingo, mhiramat, andrii
  Cc: bpf, linux-trace-kernel, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260521124411.31133-9-jolsa@kernel.org>

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

> diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> index c944136252c6..9653fb5608f2 100644
> --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c

[ ... ]

> @@ -388,14 +388,15 @@ static int find_uprobes_trampoline(void *tramp_addr)
>  	return ret;
>  }
>
> -static unsigned char nop5[5] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 };
> +static unsigned char nop10[10]  = { 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 };
> +static unsigned char lea_rsp[5] = { 0x48, 0x8d, 0x64, 0x24, 0x80 };
>
> -static void *find_nop5(void *fn)
> +static void *find_nop10(void *fn)
>  {
>  	int i;
>
> -	for (i = 0; i < 10; i++) {
> -		if (!memcmp(nop5, fn + i, 5))
> +	for (i = 0; i < 128; i++) {
> +		if (!memcmp(nop10, fn + i, 10))
>  			return fn + i;
>  	}
>  	return NULL;

[ ... ]

> @@ -432,7 +434,7 @@ static void check_detach(void *addr, void *tramp)
>  {
>  	/* [uprobes_trampoline] stays after detach */
>  	ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline");
> -	ASSERT_OK(memcmp(addr, nop5, 5), "nop5");
> +	ASSERT_OK(memcmp(addr, jmp2B, 2), "jmp2B");
>  }

Is jmp2B declared somewhere? The identifier is used in memcmp() but does
not appear to be defined anywhere in the file or included headers. A
subsequent commit later in the series replaces this with a properly
declared nop10_prefix array, confirming this is likely a missing
definition.

The pre-patch code referenced the defined static array nop5, but the new
code references an undeclared symbol that will cause a compilation error.

> diff --git a/tools/testing/selftests/bpf/prog_tests/usdt.c b/tools/testing/selftests/bpf/prog_tests/usdt.c
> index 69759b27794d..fda3a298ccfc 100644
> --- a/tools/testing/selftests/bpf/prog_tests/usdt.c
> +++ b/tools/testing/selftests/bpf/prog_tests/usdt.c
> @@ -252,7 +252,7 @@ extern void usdt_1(void);
>  extern void usdt_2(void);
>
>  static unsigned char nop1[1] = { 0x90 };
> -static unsigned char nop1_nop5_combo[6] = { 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x00 };
> +static unsigned char nop1_nop10_combo[11] = { 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 };
>
>  static void *find_instr(void *fn, unsigned char *instr, size_t cnt)
>  {
>  	int i;
>
>  	for (i = 0; i < 10; i++) {
>  		if (!memcmp(instr, fn + i, cnt))
>  			return fn + i;
>  	}
>  	return NULL;
>  }

Is the loop in find_instr() adequate to find this instruction sequence?

In uprobe_syscall.c, find_nop10() was updated to search up to 128 bytes
to account for compiler-generated prologues:

tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c:find_nop10() {
	for (i = 0; i < 128; i++) {
		if (!memcmp(nop10, fn + i, 10))
			return fn + i;
	}
}

If a modern compiler generates a prologue longer than 9 bytes for
usdt_2(), find_instr() will prematurely terminate and cause the
subtest_optimized_attach() test to fail. This was acknowledged in v2
review with "yea find_instr needs same update, will fix" but remains
unchanged.

[ ... ]


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26227520868

^ permalink raw reply

* Re: [PATCHv3 06/12] libbpf: Detect uprobe syscall with new error
From: bot+bpf-ci @ 2026-05-21 13:26 UTC (permalink / raw)
  To: jolsa, oleg, peterz, mingo, mhiramat, andrii
  Cc: bpf, linux-trace-kernel, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260521124411.31133-7-jolsa@kernel.org>

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

> commit adef46acd6e910a58509f054ca1d94c79b4c2bf2
> Author: Jiri Olsa <jolsa@kernel.org>
> Date:   Thu May 21 14:44:05 2026 +0200
>
>     libbpf: Detect uprobe syscall with new error
>
>     In the previous optimized uprobe fix we changed the syscall
>     error used for its detection from ENXIO to EPROTO.
>
>     Changing related probe_uprobe_syscall detection check.
>
>     Acked-by: Andrii Nakryiko <andrii@kernel.org>
>     Fixes: 05738da0efa1 ("libbpf: Add uprobe syscall feature detection")
>     Signed-off-by: Jiri Olsa <jolsa@kernel.org>

The Fixes tag points to commit 05738da0efa1 ("libbpf: Add uprobe syscall
feature detection"), but wasn't the bug actually introduced by commit
40b54d7e3dab ("uprobes/x86: Move optimized uprobe from nop5 to nop10")?

Commit 05738da0efa1 originally added the ENXIO check correctly, but commit
40b54d7e3dab changed the kernel to return EPROTO instead of ENXIO, breaking
the libbpf detection.

The commit message even says "In the previous optimized uprobe fix we changed
the syscall error," which refers to 40b54d7e3dab.

Should this use:

  Fixes: 40b54d7e3dab ("uprobes/x86: Move optimized uprobe from nop5 to nop10")

This concern was previously raised in review by bot+bpf-ci@kernel.org on
Mon, 18 May 2026 11:37:54 +0000:

https://lore.kernel.org/bpf/545ca16cc113f00a390baf1ffcafba33233af771fc7c879e3c850bb33949da29@mail.kernel.org/

> diff --git a/tools/lib/bpf/features.c b/tools/lib/bpf/features.c
> index b7e388f99d0b..e5641fa60163 100644
> --- a/tools/lib/bpf/features.c
> +++ b/tools/lib/bpf/features.c
> @@ -577,10 +577,10 @@ static int probe_ldimm64_full_range_off(int token_fd)
>  static int probe_uprobe_syscall(int token_fd)
>  {
>  	/*
> -	 * If kernel supports uprobe() syscall, it will return -ENXIO when called
> +	 * If kernel supports uprobe() syscall, it will return -EPROTO when called
>  	 * from the outside of a kernel-generated uprobe trampoline.
>  	 */
> -	return syscall(__NR_uprobe) < 0 && errno == ENXIO;
> +	return syscall(__NR_uprobe) < 0 && errno == EPROTO;
>  }
>  #else
>  static int probe_uprobe_syscall(int token_fd)

[ ... ]


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26227520868

^ permalink raw reply

* Re: [PATCHv3 07/12] selftests/bpf: Emit nop,nop10 instructions combo for x86_64 arch
From: bot+bpf-ci @ 2026-05-21 13:26 UTC (permalink / raw)
  To: jolsa, oleg, peterz, mingo, mhiramat, andrii
  Cc: bpf, linux-trace-kernel, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260521124411.31133-8-jolsa@kernel.org>

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

> diff --git a/tools/testing/selftests/bpf/usdt.h b/tools/testing/selftests/bpf/usdt.h
> index c71e21df38b3..75687f50f4e2 100644
> --- a/tools/testing/selftests/bpf/usdt.h
> +++ b/tools/testing/selftests/bpf/usdt.h
> @@ -313,7 +313,7 @@ struct usdt_sema { volatile unsigned short active; };
>  #if defined(__ia64__) || defined(__s390__) || defined(__s390x__)
>  #define USDT_NOP			nop 0
>  #elif defined(__x86_64__)
> -#define USDT_NOP                       .byte 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x0 /* nop, nop5 */
> +#define USDT_NOP                       .byte 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 /* nop, nop10 */
>  #else
>  #define USDT_NOP			nop
>  #endif

Does changing this instruction sequence from 6 bytes to 11 bytes break
prog_tests/usdt.c? The test in subtest_optimized_attach() searches for the
old 6-byte nop1_nop5_combo sequence:

tools/testing/selftests/bpf/prog_tests/usdt.c:
    /* usdt_2 USDT probe has nop,nop5 instructions combo */
    addr_2 = find_instr(usdt_2, nop1_nop5_combo, 6);
    if (!ASSERT_OK_PTR(addr_2, "usdt_2_find_nop1_nop5_combo"))
        goto cleanup;

Because the old sequence is no longer emitted by USDT_NOP, find_instr() will
return NULL, causing the ASSERT_OK_PTR assertion to fail. Should the test
update be squashed into this commit to avoid breaking git bisect?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26227520868

^ permalink raw reply

* Re: [PATCH v6 21/43] KVM: SEV: Make 'uaddr' parameter optional for KVM_SEV_SNP_LAUNCH_UPDATE
From: Sean Christopherson @ 2026-05-21 13:21 UTC (permalink / raw)
  To: Fuad Tabba
  Cc: ackerleytng, aik, andrew.jones, binbin.wu, brauner, chao.p.peng,
	david, ira.weiny, jmattson, jthoughton, michael.roth, oupton,
	pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
	steven.price, willy, wyihan, yan.y.zhao, forkloop, pratyush,
	suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, Shuah Khan,
	Vishal Annapurve, Andrew Morton, Chris Li, Kairui Song,
	Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Axel Rasmussen,
	Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng, Shakeel Butt,
	Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka, kvm,
	linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
	linux-mm, linux-coco
In-Reply-To: <CA+EHjTwrygfMrZZSw4y7-ry8fidW2x0C7iuF2Q=dnPNHUmNtUg@mail.gmail.com>

On Thu, May 21, 2026, Fuad Tabba wrote:
> Hi,
> 
> On Thu, 7 May 2026 at 21:22, Ackerley Tng via B4 Relay
> <devnull+ackerleytng.google.com@kernel.org> wrote:
> >
> > From: Michael Roth <michael.roth@amd.com>
> >
> > For vm_memory_attributes=1, in-place conversion/population is not
> > supported, so the initial contents necessarily must need to come
> > from a separate src address, which is enforced by the current
> > implementation. However, for vm_memory_attributes=0, it is possible for
> > guest memory to be initialized directly from userspace by mmap()'ing the
> > guest_memfd and writing to it while the corresponding GPA ranges are in
> > a 'shared' state before converting them to the 'private' state expected
> > by KVM_SEV_SNP_LAUNCH_UPDATE.
> >
> > Update the handling/documentation for KVM_SEV_SNP_LAUNCH_UPDATE to allow
> > for 'uaddr' to be set to NULL when vm_memory_attributes=0, which
> > SNP_LAUNCH_UPDATE will then use to determine when it should/shouldn't
> > copy in data from a separate memory location. Continue to enforce
> > non-NULL for the original vm_memory_attributes=1 case.
> >
> > Signed-off-by: Michael Roth <michael.roth@amd.com>
> > [Added src_page check in error handling path when the firmware command fails]
> > [Dropped ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES]
> > Signed-off-by: Ackerley Tng <ackerleytng@google.com>
> 
> I'm not very familiar with the SEV-SNP populate flows, but it looks
> like Sashiko is on to something:
> https://sashiko.dev/#/patchset/20260507-gmem-inplace-conversion-v6-0-91ab5a8b19a4%40google.com?part=21
> 
> - a potential read-only page overwrite, because src_page is acquired
> via get_user_pages_fast() without the FOLL_WRITE flag, but is then
> overwritten via memcpy

Oof, yeah, that's bad.  Adding FOLL_WRITE to kvm_gmem_populate() feels wrong, and
could break uABI, but doing gup() in SNP code would reintroduce the AB-BA issue
with filemap_invalidate_lock().

Aha!  Not if we use get_user_page_fast_only().  Ugh, but then we'd have to plumb
the userspace address into the post-populated callback.

Hrm.  Given that no one has yelled about overwriting their CPUID page, and given
that the CPUID page is likely dynamically created and thus is unlikely to be a
read-only mapping (e.g. versus the initial image), maybe this?

diff --git arch/x86/kvm/svm/sev.c arch/x86/kvm/svm/sev.c
index 37d4cfa5d980..c73c028d72c1 100644
--- arch/x86/kvm/svm/sev.c
+++ arch/x86/kvm/svm/sev.c
@@ -2456,6 +2456,7 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
        sev_populate_args.type = params.type;
 
        count = kvm_gmem_populate(kvm, params.gfn_start, src, npages,
+                                 params.type == KVM_SEV_SNP_PAGE_TYPE_CPUID,
                                  sev_gmem_post_populate, &sev_populate_args);
        if (count < 0) {
                argp->error = sev_populate_args.fw_error;
diff --git arch/x86/kvm/vmx/tdx.c arch/x86/kvm/vmx/tdx.c
index f97bcf580e6d..33f35be4455b 100644
--- arch/x86/kvm/vmx/tdx.c
+++ arch/x86/kvm/vmx/tdx.c
@@ -3188,7 +3188,7 @@ static int tdx_vcpu_init_mem_region(struct kvm_vcpu *vcpu, struct kvm_tdx_cmd *c
                };
                gmem_ret = kvm_gmem_populate(kvm, gpa_to_gfn(region.gpa),
                                             u64_to_user_ptr(region.source_addr),
-                                            1, tdx_gmem_post_populate, &arg);
+                                            1, false, tdx_gmem_post_populate, &arg);
                if (gmem_ret < 0) {
                        ret = gmem_ret;
                        break;
diff --git include/linux/kvm_host.h include/linux/kvm_host.h
index 61a3430957f2..b83cda2870ba 100644
--- include/linux/kvm_host.h
+++ include/linux/kvm_host.h
@@ -2596,7 +2596,8 @@ int kvm_arch_gmem_prepare(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn, int max_ord
 typedef int (*kvm_gmem_populate_cb)(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
                                    struct page *page, void *opaque);
 
-long kvm_gmem_populate(struct kvm *kvm, gfn_t gfn, void __user *src, long npages,
+long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src,
+                      long npages, bool writable,
                       kvm_gmem_populate_cb post_populate, void *opaque);
 #endif
 
diff --git virt/kvm/guest_memfd.c virt/kvm/guest_memfd.c
index a35a55571a2d..6553d4e032ce 100644
--- virt/kvm/guest_memfd.c
+++ virt/kvm/guest_memfd.c
@@ -858,7 +858,8 @@ static long __kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot,
        return ret;
 }
 
-long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, long npages,
+long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src,
+                      long npages, bool writable,
                       kvm_gmem_populate_cb post_populate, void *opaque)
 {
        struct kvm_memory_slot *slot;
@@ -892,8 +893,9 @@ long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, long
 
                if (src) {
                        unsigned long uaddr = (unsigned long)src + i * PAGE_SIZE;
+                       unsigned int flags = writable ? FOLL_WRITE : 0;
 
-                       ret = get_user_pages_fast(uaddr, 1, 0, &src_page);
+                       ret = get_user_pages_fast(uaddr, 1, flags, &src_page);
                        if (ret < 0)
                                break;
                        if (ret != 1) {

> - an ordering violation with the kunmap_local() calls

Yeesh, that's a new one for me.  Thankfully this is 64-bit only, so it's not an
issue.

> These predate this patch series and are just being touched by the
> 'src_page' addition, but if Sashiko's right, these should probably be
> fixed sooner rather than later.

Yeah, ditto with the offset wrapping case.

^ permalink raw reply related

* [PATCH v6] stm: class: Add MIPI OST protocol support
From: Yingchao Deng @ 2026-05-21 13:14 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Jonathan Corbet, Shuah Khan, Alexander Shishkin, Alexandre Torgue
  Cc: linux-kernel, linux-trace-kernel, linux-doc, linux-arm-kernel,
	quic_yingdeng, tingwei.zhang, jinlong.mao, jie.gan,
	yuanfang.zhang, Yingchao Deng

Add MIPI OST (Open System Trace) protocol support for stm to format the
traces. The OST Protocol abstracts the underlying layers from the sending
and receiving applications, thus removing dependencies on the connection
media and platform implementation.

OST over STP packet consists of Header/Payload/End. Header is designed to
include the information required by all OST packets. Information that is
not shared by all packets is left to the higher layer protocols. Thus, the
OST Protocol Header can be regarded as the first part of a complete OST
Packet Header, while a higher layer header can be regarded as an extension
designed for a specific purpose.

+--------+--------+--------+--------+
| start  |version |entity  |protocol|
+--------+--------+--------+--------+
|    stm version  |      magic      |
+-----------------------------------+
|                cpu                |
+-----------------------------------+
|              timestamp            |
|                                   |
+-----------------------------------+
|                tgid               |
|                                   |
+-----------------------------------+
|               payload             |
+-----------------------------------+
|                 ...      |  end   |
+-----------------------------------+

In header, there will be STARTSIMPLE/VERSION/ENTITY/PROTOCOL.
STARTSIMPLE is used to signal the beginning of a simplified OST protocol.
The Version field is a one byte, unsigned number identifying the version
of the OST Protocol. The Entity ID field is a one byte unsigned number
that identifies the source.

Entity ID values (0~239) are defined and controlled by the TS owner, and
shall be unique for the whole TS. The configfs entity attribute allows the
user to configure which Entity ID is associated with each policy node.

The Protocol ID field is a one byte unsigned number identifying the higher
layer protocol of the OST Packet, i.e. identifying the format of the data
after the OST Protocol Header. OST Control Protocol ID value represents
the common control protocol, the remaining Protocol ID values may be used
by any higher layer protocols capable of being transported by the OST
Protocol.

Co-developed-by: Tingwei Zhang <tingwei.zhang@oss.qualcomm.com>
Signed-off-by: Tingwei Zhang <tingwei.zhang@oss.qualcomm.com>
Co-developed-by: Yuanfang Zhang <yuanfang.zhang@oss.qualcomm.com>
Signed-off-by: Yuanfang Zhang <yuanfang.zhang@oss.qualcomm.com>
Co-developed-by: Jinlong Mao <jinlong.mao@oss.qualcomm.com>
Signed-off-by: Jinlong Mao <jinlong.mao@oss.qualcomm.com>
Signed-off-by: Yingchao Deng <yingchao.deng@oss.qualcomm.com>
---
Changes in v6:
1. Rebase on top of linux-next-20260518.
2. Fix Kconfig: 'default CONFIG_STM' -> 'default STM'.
3. Fix documentation grammar issues.
4. Add p_ost entry to Documentation/trace/index.rst.
5. Add missing priv_sz field to stm_protocol_driver registration.
6. Use kzalloc_obj() instead of kzalloc() in ost_output_open().
7. Add mutex protection in entity configfs store handler.
8. Keep the configfs entity attribute: entity ID values (0~239) are
   defined and controlled by the TS owner and are deployment-specific.
   stm_source_type only carries a small number of in-kernel source
   classifications and cannot represent the full range of OST entity
   assignments needed in practice. The configfs attribute allows each
   policy node to declare its entity.
   OST_ENTITY_TYPE_NONE is an enum sentinel (not entity ID 0) that causes
   ost_write() to return -EINVAL when no entity is configured, preventing
   emission of packets with an unintended entity field.
   OST_ENTITY_DIAG (0xEE) is a TS-owner-defined value used by Qualcomm's
   diagnostic framework as the standard entity identifier for diagnostic
   trace sources.
Link to v5: https://lore.kernel.org/all/20260129-p_ost-v5-1-2b14fff39428@oss.qualcomm.com/

Changes in v5:
1. Add Co-developed-by tag.
2. Use yearless copyright for new file.
- Link to v4: https://lore.kernel.org/all/20251024-p_ost-v4-1-3652a06fd055@oss.qualcomm.com/

Changes in v4:
1. Delete unused variable 'i'.
2. Fix build error: call to undeclared function 'task_tgid_nr'.
Link to v3 - https://lore.kernel.org/all/20251022071834.1658684-1-yingchao.deng@oss.qualcomm.com/

Changes in v3:
1. Add more details about OST.
2. Delete 'entity_available' node, and 'entity' node will show available
and currently selected (shown in square brackets) entity.
3. Removed the usage of config_item->ci_group->cg_subsys->su_mutex.
Link to v2 - https://lore.kernel.org/all/20230419141328.37472-1-quic_jinlmao@quicinc.com/
---
 .../ABI/testing/configfs-stp-policy-p_ost          |   9 +
 Documentation/trace/index.rst                      |   1 +
 Documentation/trace/p_ost.rst                      |  39 ++++
 drivers/hwtracing/stm/Kconfig                      |  14 ++
 drivers/hwtracing/stm/Makefile                     |   2 +
 drivers/hwtracing/stm/p_ost.c                      | 241 +++++++++++++++++++++
 6 files changed, 306 insertions(+)

diff --git a/Documentation/ABI/testing/configfs-stp-policy-p_ost b/Documentation/ABI/testing/configfs-stp-policy-p_ost
new file mode 100644
index 000000000000..8fb160b50c40
--- /dev/null
+++ b/Documentation/ABI/testing/configfs-stp-policy-p_ost
@@ -0,0 +1,9 @@
+What:		/config/stp-policy/<device>:p_ost.<policy>/<node>/entity
+Date:		May 2026
+KernelVersion:	7.1
+Description:
+		Set the entity ID which identifies the trace source in the
+		OST packet header. Entity ID values (0~239) are defined by
+		the TS owner. Currently supported values are ftrace, console
+		and diag. RW.
+
diff --git a/Documentation/trace/index.rst b/Documentation/trace/index.rst
index 5d9bf4694d5d..9cd1e0b5af6d 100644
--- a/Documentation/trace/index.rst
+++ b/Documentation/trace/index.rst
@@ -72,6 +72,7 @@ interactions and system performance.
    intel_th
    stm
    sys-t
+   p_ost
    coresight/index
    rv/index
    hisi-ptt
diff --git a/Documentation/trace/p_ost.rst b/Documentation/trace/p_ost.rst
new file mode 100644
index 000000000000..2b92e2229653
--- /dev/null
+++ b/Documentation/trace/p_ost.rst
@@ -0,0 +1,39 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===================
+MIPI OST over STP
+===================
+
+The OST (Open System Trace) driver is used with STM class devices to
+generate standardized trace stream. Trace sources can be identified
+by different entity IDs.
+
+CONFIG_STM_PROTO_OST is for p_ost driver enablement. Once this config
+is enabled, you can select the p_ost protocol by command below:
+
+# mkdir /sys/kernel/config/stp-policy/stm0:p_ost.policy
+
+The policy name format is extended like this:
+
+  <device_name>:<protocol_name>.<policy_name>
+
+With a coresight-stm device, it will look like "stm0:p_ost.policy".
+
+With the MIPI OST protocol driver, the attributes for each protocol node are:
+
+# mkdir /sys/kernel/config/stp-policy/stm0:p_ost.policy/default
+# ls /sys/kernel/config/stp-policy/stm0:p_ost.policy/default
+channels  entity    masters
+
+The entity here is the set of entities that p_ost supports. Currently
+p_ost supports ftrace, console and diag entities.
+
+Set entity:
+# echo 'ftrace' > /sys/kernel/config/stp-policy/stm0:p_ost.policy/default/entity
+
+Get available and currently selected (shown in square brackets) entity:
+# cat /sys/kernel/config/stp-policy/stm0:p_ost.policy/default/entity
+[ftrace] console diag
+
+See Documentation/ABI/testing/configfs-stp-policy-p_ost for more details.
+
diff --git a/drivers/hwtracing/stm/Kconfig b/drivers/hwtracing/stm/Kconfig
index cd7f0b0f3fbe..4c83da5d95a0 100644
--- a/drivers/hwtracing/stm/Kconfig
+++ b/drivers/hwtracing/stm/Kconfig
@@ -40,6 +40,20 @@ config STM_PROTO_SYS_T
 
 	  If you don't know what this is, say N.
 
+config STM_PROTO_OST
+	tristate "MIPI OST STM framing protocol driver"
+	default STM
+	help
+	  This is an implementation of MIPI OST protocol to be used
+	  over the STP transport. In addition to the data payload, it
+	  also carries additional metadata for entity, better
+	  means of trace source identification, etc.
+
+	  The receiving side must be able to decode this protocol in
+	  addition to the MIPI STP, in order to extract the data.
+
+	  If you don't know what this is, say N.
+
 config STM_DUMMY
 	tristate "Dummy STM driver"
 	help
diff --git a/drivers/hwtracing/stm/Makefile b/drivers/hwtracing/stm/Makefile
index 1692fcd29277..d9c8615849b9 100644
--- a/drivers/hwtracing/stm/Makefile
+++ b/drivers/hwtracing/stm/Makefile
@@ -5,9 +5,11 @@ stm_core-y		:= core.o policy.o
 
 obj-$(CONFIG_STM_PROTO_BASIC) += stm_p_basic.o
 obj-$(CONFIG_STM_PROTO_SYS_T) += stm_p_sys-t.o
+obj-$(CONFIG_STM_PROTO_OST)   += stm_p_ost.o
 
 stm_p_basic-y		:= p_basic.o
 stm_p_sys-t-y		:= p_sys-t.o
+stm_p_ost-y		:= p_ost.o
 
 obj-$(CONFIG_STM_DUMMY)	+= dummy_stm.o
 
diff --git a/drivers/hwtracing/stm/p_ost.c b/drivers/hwtracing/stm/p_ost.c
new file mode 100644
index 000000000000..d2174872b761
--- /dev/null
+++ b/drivers/hwtracing/stm/p_ost.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * MIPI OST framing protocol for STM devices.
+ */
+
+#include <linux/pid.h>
+#include <linux/sched/clock.h>
+#include <linux/slab.h>
+#include <linux/stm.h>
+#include "stm.h"
+
+/*
+ * OST Base Protocol Header
+ *
+ * Position	Bits	Field Name
+ *      0       8       STARTSIMPLE
+ *      1       8       Version
+ *      2       8       Entity ID
+ *      3       8       Protocol ID
+ */
+#define OST_FIELD_STARTSIMPLE		0
+#define OST_FIELD_VERSION		8
+#define OST_FIELD_ENTITY		16
+#define OST_FIELD_PROTOCOL		24
+
+#define OST_TOKEN_STARTSIMPLE		0x10
+#define OST_VERSION_MIPI1		0x10
+
+/* entity id to identify the source */
+#define OST_ENTITY_FTRACE		0x01
+#define OST_ENTITY_CONSOLE		0x02
+#define OST_ENTITY_DIAG			0xEE
+
+#define OST_CONTROL_PROTOCOL		0x0
+
+#define DATA_HEADER ((OST_TOKEN_STARTSIMPLE << OST_FIELD_STARTSIMPLE) | \
+		     (OST_VERSION_MIPI1 << OST_FIELD_VERSION) | \
+		     (OST_CONTROL_PROTOCOL << OST_FIELD_PROTOCOL))
+
+#define STM_MAKE_VERSION(ma, mi)	(((ma) << 8) | (mi))
+#define STM_HEADER_MAGIC		(0x5953)
+
+enum ost_entity_type {
+	OST_ENTITY_TYPE_NONE,
+	OST_ENTITY_TYPE_FTRACE,
+	OST_ENTITY_TYPE_CONSOLE,
+	OST_ENTITY_TYPE_DIAG,
+};
+
+static const char * const str_ost_entity_type[] = {
+	[OST_ENTITY_TYPE_NONE]		= "none",
+	[OST_ENTITY_TYPE_FTRACE]	= "ftrace",
+	[OST_ENTITY_TYPE_CONSOLE]	= "console",
+	[OST_ENTITY_TYPE_DIAG]		= "diag",
+};
+
+static const u32 ost_entity_value[] = {
+	[OST_ENTITY_TYPE_NONE]		= 0,
+	[OST_ENTITY_TYPE_FTRACE]	= OST_ENTITY_FTRACE,
+	[OST_ENTITY_TYPE_CONSOLE]	= OST_ENTITY_CONSOLE,
+	[OST_ENTITY_TYPE_DIAG]		= OST_ENTITY_DIAG,
+};
+
+struct ost_policy_node {
+	enum ost_entity_type	entity_type;
+};
+
+struct ost_output {
+	struct ost_policy_node	node;
+};
+
+/* Set default entity type as none */
+static void ost_policy_node_init(void *priv)
+{
+	struct ost_policy_node *pn = priv;
+
+	pn->entity_type = OST_ENTITY_TYPE_NONE;
+}
+
+static int ost_output_open(void *priv, struct stm_output *output)
+{
+	struct ost_policy_node *pn = priv;
+	struct ost_output *opriv;
+
+	opriv = kzalloc_obj(*opriv, GFP_ATOMIC);
+	if (!opriv)
+		return -ENOMEM;
+
+	memcpy(&opriv->node, pn, sizeof(opriv->node));
+	output->pdrv_private = opriv;
+	return 0;
+}
+
+static void ost_output_close(struct stm_output *output)
+{
+	kfree(output->pdrv_private);
+}
+
+static ssize_t ost_t_policy_entity_show(struct config_item *item,
+					char *page)
+{
+	struct ost_policy_node *pn = to_pdrv_policy_node(item);
+	ssize_t sz = 0;
+	int i;
+
+	for (i = 1; i < ARRAY_SIZE(str_ost_entity_type); i++) {
+		if (i == pn->entity_type)
+			sz += sysfs_emit_at(page, sz, "[%s] ", str_ost_entity_type[i]);
+		else
+			sz += sysfs_emit_at(page, sz, "%s ", str_ost_entity_type[i]);
+	}
+
+	sz += sysfs_emit_at(page, sz, "\n");
+	return sz;
+}
+
+static int entity_index(const char *str)
+{
+	int i;
+
+	for (i = 1; i < ARRAY_SIZE(str_ost_entity_type); i++) {
+		if (sysfs_streq(str, str_ost_entity_type[i]))
+			return i;
+	}
+
+	return 0;
+}
+
+static ssize_t
+ost_t_policy_entity_store(struct config_item *item, const char *page,
+			  size_t count)
+{
+	struct mutex *mutexp = &item->ci_group->cg_subsys->su_mutex;
+	struct ost_policy_node *pn = to_pdrv_policy_node(item);
+	int i;
+
+	i = entity_index(page);
+	if (i) {
+		mutex_lock(mutexp);
+		pn->entity_type = i;
+		mutex_unlock(mutexp);
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+CONFIGFS_ATTR(ost_t_policy_, entity);
+
+static struct configfs_attribute *ost_t_policy_attrs[] = {
+	&ost_t_policy_attr_entity,
+	NULL,
+};
+
+static ssize_t
+notrace ost_write(struct stm_data *data, struct stm_output *output,
+		  unsigned int chan, const char *buf, size_t count,
+		  struct stm_source_data *source)
+{
+	struct ost_output *op = output->pdrv_private;
+	unsigned int c = output->channel + chan;
+	unsigned int m = output->master;
+	const unsigned char nil = 0;
+	u32 header = DATA_HEADER;
+	struct trc_hdr {
+		u16 version;
+		u16 magic;
+		u32 cpu;
+		u64 timestamp;
+		u64 tgid;
+	} hdr;
+	ssize_t sz;
+
+	/*
+	 * Identify the source by entity type.
+	 * If entity type is not set, return error value.
+	 */
+	if (op->node.entity_type)
+		header |= (ost_entity_value[op->node.entity_type] << OST_FIELD_ENTITY);
+	else
+		return -EINVAL;
+
+	/*
+	 * STP framing rules for OST frames:
+	 *   * the first packet of the OST frame is marked;
+	 *   * the last packet is a FLAG with timestamped tag.
+	 */
+	/* Message layout: HEADER / DATA / TAIL */
+	/* HEADER */
+	sz = data->packet(data, m, c, STP_PACKET_DATA, STP_PACKET_MARKED,
+			  4, (u8 *)&header);
+	if (sz <= 0)
+		return sz;
+
+	/* DATA */
+	hdr.version	= STM_MAKE_VERSION(0, 3);
+	hdr.magic	= STM_HEADER_MAGIC;
+	hdr.cpu		= raw_smp_processor_id();
+	hdr.timestamp	= sched_clock();
+	hdr.tgid	= task_tgid_nr(current);
+	sz = stm_data_write(data, m, c, false, &hdr, sizeof(hdr));
+	if (sz <= 0)
+		return sz;
+
+	sz = stm_data_write(data, m, c, false, buf, count);
+
+	/* TAIL */
+	if (sz > 0)
+		data->packet(data, m, c, STP_PACKET_FLAG,
+			STP_PACKET_TIMESTAMPED, 0, &nil);
+
+	return sz;
+}
+
+static const struct stm_protocol_driver ost_pdrv = {
+	.owner			= THIS_MODULE,
+	.name			= "p_ost",
+	.priv_sz		= sizeof(struct ost_policy_node),
+	.write			= ost_write,
+	.policy_attr		= ost_t_policy_attrs,
+	.output_open		= ost_output_open,
+	.output_close		= ost_output_close,
+	.policy_node_init	= ost_policy_node_init,
+};
+
+static int ost_stm_init(void)
+{
+	return stm_register_protocol(&ost_pdrv);
+}
+module_init(ost_stm_init);
+
+static void ost_stm_exit(void)
+{
+	stm_unregister_protocol(&ost_pdrv);
+}
+module_exit(ost_stm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MIPI Open System Trace STM framing protocol driver");

---
base-commit: 80dd246accce631c328ea43294e53b2b2dd2aa32
change-id: 20260521-stm_p_ost-3489f42a9e8c

Best regards,
-- 
Yingchao Deng <yingchao.deng@oss.qualcomm.com>


^ permalink raw reply related

* [PATCH v6] stm: class: Add MIPI OST protocol support
From: Yingchao Deng @ 2026-05-21 13:08 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Jonathan Corbet, Shuah Khan, Alexander Shishkin, Alexandre Torgue
  Cc: linux-kernel, linux-trace-kernel, linux-doc, linux-arm-kernel,
	Tingwei Zhang, Yuanfang Zhang, Jinlong Mao, Yingchao Deng

Add MIPI OST (Open System Trace) protocol support for stm to format the
traces. The OST Protocol abstracts the underlying layers from the sending
and receiving applications, thus removing dependencies on the connection
media and platform implementation.

OST over STP packet consists of Header/Payload/End. Header is designed to
include the information required by all OST packets. Information that is
not shared by all packets is left to the higher layer protocols. Thus, the
OST Protocol Header can be regarded as the first part of a complete OST
Packet Header, while a higher layer header can be regarded as an extension
designed for a specific purpose.

+--------+--------+--------+--------+
| start  |version |entity  |protocol|
+--------+--------+--------+--------+
|    stm version  |      magic      |
+-----------------------------------+
|                cpu                |
+-----------------------------------+
|              timestamp            |
|                                   |
+-----------------------------------+
|                tgid               |
|                                   |
+-----------------------------------+
|               payload             |
+-----------------------------------+
|                 ...      |  end   |
+-----------------------------------+

In header, there will be STARTSIMPLE/VERSION/ENTITY/PROTOCOL.
STARTSIMPLE is used to signal the beginning of a simplified OST protocol.
The Version field is a one byte, unsigned number identifying the version
of the OST Protocol. The Entity ID field is a one byte unsigned number
that identifies the source.

Entity ID values (0~239) are defined and controlled by the TS owner, and
shall be unique for the whole TS. The configfs entity attribute allows the
user to configure which Entity ID is associated with each policy node.

The Protocol ID field is a one byte unsigned number identifying the higher
layer protocol of the OST Packet, i.e. identifying the format of the data
after the OST Protocol Header. OST Control Protocol ID value represents
the common control protocol, the remaining Protocol ID values may be used
by any higher layer protocols capable of being transported by the OST
Protocol.

Co-developed-by: Tingwei Zhang <tingwei.zhang@oss.qualcomm.com>
Signed-off-by: Tingwei Zhang <tingwei.zhang@oss.qualcomm.com>
Co-developed-by: Yuanfang Zhang <yuanfang.zhang@oss.qualcomm.com>
Signed-off-by: Yuanfang Zhang <yuanfang.zhang@oss.qualcomm.com>
Co-developed-by: Jinlong Mao <jinlong.mao@oss.qualcomm.com>
Signed-off-by: Jinlong Mao <jinlong.mao@oss.qualcomm.com>
Signed-off-by: Yingchao Deng <yingchao.deng@oss.qualcomm.com>
---
Changes in v6:
1. Rebase on top of linux-next-20260518.
2. Fix Kconfig: 'default CONFIG_STM' -> 'default STM'.
3. Fix documentation grammar issues.
4. Add p_ost entry to Documentation/trace/index.rst.
5. Add missing priv_sz field to stm_protocol_driver registration.
6. Use kzalloc_obj() instead of kzalloc() in ost_output_open().
7. Add mutex protection in entity configfs store handler.
8. Keep the configfs entity attribute: entity ID values (0~239) are
   defined and controlled by the TS owner and are deployment-specific.
   stm_source_type only carries a small number of in-kernel source
   classifications and cannot represent the full range of OST entity
   assignments needed in practice. The configfs attribute allows each
   policy node to declare its entity.
   OST_ENTITY_TYPE_NONE is an enum sentinel (not entity ID 0) that causes
   ost_write() to return -EINVAL when no entity is configured, preventing
   emission of packets with an unintended entity field.
   OST_ENTITY_DIAG (0xEE) is a TS-owner-defined value used by Qualcomm's
   diagnostic framework as the standard entity identifier for diagnostic
   trace sources.
Link to v5: https://lore.kernel.org/all/20260129-p_ost-v5-1-2b14fff39428@oss.qualcomm.com/

Changes in v5:
1. Add Co-developed-by tag.
2. Use yearless copyright for new file.
- Link to v4: https://lore.kernel.org/all/20251024-p_ost-v4-1-3652a06fd055@oss.qualcomm.com/

Changes in v4:
1. Delete unused variable 'i'.
2. Fix build error: call to undeclared function 'task_tgid_nr'.
Link to v3 - https://lore.kernel.org/all/20251022071834.1658684-1-yingchao.deng@oss.qualcomm.com/

Changes in v3:
1. Add more details about OST.
2. Delete 'entity_available' node, and 'entity' node will show available
and currently selected (shown in square brackets) entity.
3. Removed the usage of config_item->ci_group->cg_subsys->su_mutex.
Link to v2 - https://lore.kernel.org/all/20230419141328.37472-1-quic_jinlmao@quicinc.com/
---
 .../ABI/testing/configfs-stp-policy-p_ost          |   9 +
 Documentation/trace/index.rst                      |   1 +
 Documentation/trace/p_ost.rst                      |  39 ++++
 drivers/hwtracing/stm/Kconfig                      |  14 ++
 drivers/hwtracing/stm/Makefile                     |   2 +
 drivers/hwtracing/stm/p_ost.c                      | 241 +++++++++++++++++++++
 6 files changed, 306 insertions(+)

diff --git a/Documentation/ABI/testing/configfs-stp-policy-p_ost b/Documentation/ABI/testing/configfs-stp-policy-p_ost
new file mode 100644
index 000000000000..8fb160b50c40
--- /dev/null
+++ b/Documentation/ABI/testing/configfs-stp-policy-p_ost
@@ -0,0 +1,9 @@
+What:		/config/stp-policy/<device>:p_ost.<policy>/<node>/entity
+Date:		May 2026
+KernelVersion:	7.1
+Description:
+		Set the entity ID which identifies the trace source in the
+		OST packet header. Entity ID values (0~239) are defined by
+		the TS owner. Currently supported values are ftrace, console
+		and diag. RW.
+
diff --git a/Documentation/trace/index.rst b/Documentation/trace/index.rst
index 5d9bf4694d5d..9cd1e0b5af6d 100644
--- a/Documentation/trace/index.rst
+++ b/Documentation/trace/index.rst
@@ -72,6 +72,7 @@ interactions and system performance.
    intel_th
    stm
    sys-t
+   p_ost
    coresight/index
    rv/index
    hisi-ptt
diff --git a/Documentation/trace/p_ost.rst b/Documentation/trace/p_ost.rst
new file mode 100644
index 000000000000..2b92e2229653
--- /dev/null
+++ b/Documentation/trace/p_ost.rst
@@ -0,0 +1,39 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===================
+MIPI OST over STP
+===================
+
+The OST (Open System Trace) driver is used with STM class devices to
+generate standardized trace stream. Trace sources can be identified
+by different entity IDs.
+
+CONFIG_STM_PROTO_OST is for p_ost driver enablement. Once this config
+is enabled, you can select the p_ost protocol by command below:
+
+# mkdir /sys/kernel/config/stp-policy/stm0:p_ost.policy
+
+The policy name format is extended like this:
+
+  <device_name>:<protocol_name>.<policy_name>
+
+With a coresight-stm device, it will look like "stm0:p_ost.policy".
+
+With the MIPI OST protocol driver, the attributes for each protocol node are:
+
+# mkdir /sys/kernel/config/stp-policy/stm0:p_ost.policy/default
+# ls /sys/kernel/config/stp-policy/stm0:p_ost.policy/default
+channels  entity    masters
+
+The entity here is the set of entities that p_ost supports. Currently
+p_ost supports ftrace, console and diag entities.
+
+Set entity:
+# echo 'ftrace' > /sys/kernel/config/stp-policy/stm0:p_ost.policy/default/entity
+
+Get available and currently selected (shown in square brackets) entity:
+# cat /sys/kernel/config/stp-policy/stm0:p_ost.policy/default/entity
+[ftrace] console diag
+
+See Documentation/ABI/testing/configfs-stp-policy-p_ost for more details.
+
diff --git a/drivers/hwtracing/stm/Kconfig b/drivers/hwtracing/stm/Kconfig
index cd7f0b0f3fbe..4c83da5d95a0 100644
--- a/drivers/hwtracing/stm/Kconfig
+++ b/drivers/hwtracing/stm/Kconfig
@@ -40,6 +40,20 @@ config STM_PROTO_SYS_T
 
 	  If you don't know what this is, say N.
 
+config STM_PROTO_OST
+	tristate "MIPI OST STM framing protocol driver"
+	default STM
+	help
+	  This is an implementation of MIPI OST protocol to be used
+	  over the STP transport. In addition to the data payload, it
+	  also carries additional metadata for entity, better
+	  means of trace source identification, etc.
+
+	  The receiving side must be able to decode this protocol in
+	  addition to the MIPI STP, in order to extract the data.
+
+	  If you don't know what this is, say N.
+
 config STM_DUMMY
 	tristate "Dummy STM driver"
 	help
diff --git a/drivers/hwtracing/stm/Makefile b/drivers/hwtracing/stm/Makefile
index 1692fcd29277..d9c8615849b9 100644
--- a/drivers/hwtracing/stm/Makefile
+++ b/drivers/hwtracing/stm/Makefile
@@ -5,9 +5,11 @@ stm_core-y		:= core.o policy.o
 
 obj-$(CONFIG_STM_PROTO_BASIC) += stm_p_basic.o
 obj-$(CONFIG_STM_PROTO_SYS_T) += stm_p_sys-t.o
+obj-$(CONFIG_STM_PROTO_OST)   += stm_p_ost.o
 
 stm_p_basic-y		:= p_basic.o
 stm_p_sys-t-y		:= p_sys-t.o
+stm_p_ost-y		:= p_ost.o
 
 obj-$(CONFIG_STM_DUMMY)	+= dummy_stm.o
 
diff --git a/drivers/hwtracing/stm/p_ost.c b/drivers/hwtracing/stm/p_ost.c
new file mode 100644
index 000000000000..d2174872b761
--- /dev/null
+++ b/drivers/hwtracing/stm/p_ost.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * MIPI OST framing protocol for STM devices.
+ */
+
+#include <linux/pid.h>
+#include <linux/sched/clock.h>
+#include <linux/slab.h>
+#include <linux/stm.h>
+#include "stm.h"
+
+/*
+ * OST Base Protocol Header
+ *
+ * Position	Bits	Field Name
+ *      0       8       STARTSIMPLE
+ *      1       8       Version
+ *      2       8       Entity ID
+ *      3       8       Protocol ID
+ */
+#define OST_FIELD_STARTSIMPLE		0
+#define OST_FIELD_VERSION		8
+#define OST_FIELD_ENTITY		16
+#define OST_FIELD_PROTOCOL		24
+
+#define OST_TOKEN_STARTSIMPLE		0x10
+#define OST_VERSION_MIPI1		0x10
+
+/* entity id to identify the source */
+#define OST_ENTITY_FTRACE		0x01
+#define OST_ENTITY_CONSOLE		0x02
+#define OST_ENTITY_DIAG			0xEE
+
+#define OST_CONTROL_PROTOCOL		0x0
+
+#define DATA_HEADER ((OST_TOKEN_STARTSIMPLE << OST_FIELD_STARTSIMPLE) | \
+		     (OST_VERSION_MIPI1 << OST_FIELD_VERSION) | \
+		     (OST_CONTROL_PROTOCOL << OST_FIELD_PROTOCOL))
+
+#define STM_MAKE_VERSION(ma, mi)	(((ma) << 8) | (mi))
+#define STM_HEADER_MAGIC		(0x5953)
+
+enum ost_entity_type {
+	OST_ENTITY_TYPE_NONE,
+	OST_ENTITY_TYPE_FTRACE,
+	OST_ENTITY_TYPE_CONSOLE,
+	OST_ENTITY_TYPE_DIAG,
+};
+
+static const char * const str_ost_entity_type[] = {
+	[OST_ENTITY_TYPE_NONE]		= "none",
+	[OST_ENTITY_TYPE_FTRACE]	= "ftrace",
+	[OST_ENTITY_TYPE_CONSOLE]	= "console",
+	[OST_ENTITY_TYPE_DIAG]		= "diag",
+};
+
+static const u32 ost_entity_value[] = {
+	[OST_ENTITY_TYPE_NONE]		= 0,
+	[OST_ENTITY_TYPE_FTRACE]	= OST_ENTITY_FTRACE,
+	[OST_ENTITY_TYPE_CONSOLE]	= OST_ENTITY_CONSOLE,
+	[OST_ENTITY_TYPE_DIAG]		= OST_ENTITY_DIAG,
+};
+
+struct ost_policy_node {
+	enum ost_entity_type	entity_type;
+};
+
+struct ost_output {
+	struct ost_policy_node	node;
+};
+
+/* Set default entity type as none */
+static void ost_policy_node_init(void *priv)
+{
+	struct ost_policy_node *pn = priv;
+
+	pn->entity_type = OST_ENTITY_TYPE_NONE;
+}
+
+static int ost_output_open(void *priv, struct stm_output *output)
+{
+	struct ost_policy_node *pn = priv;
+	struct ost_output *opriv;
+
+	opriv = kzalloc_obj(*opriv, GFP_ATOMIC);
+	if (!opriv)
+		return -ENOMEM;
+
+	memcpy(&opriv->node, pn, sizeof(opriv->node));
+	output->pdrv_private = opriv;
+	return 0;
+}
+
+static void ost_output_close(struct stm_output *output)
+{
+	kfree(output->pdrv_private);
+}
+
+static ssize_t ost_t_policy_entity_show(struct config_item *item,
+					char *page)
+{
+	struct ost_policy_node *pn = to_pdrv_policy_node(item);
+	ssize_t sz = 0;
+	int i;
+
+	for (i = 1; i < ARRAY_SIZE(str_ost_entity_type); i++) {
+		if (i == pn->entity_type)
+			sz += sysfs_emit_at(page, sz, "[%s] ", str_ost_entity_type[i]);
+		else
+			sz += sysfs_emit_at(page, sz, "%s ", str_ost_entity_type[i]);
+	}
+
+	sz += sysfs_emit_at(page, sz, "\n");
+	return sz;
+}
+
+static int entity_index(const char *str)
+{
+	int i;
+
+	for (i = 1; i < ARRAY_SIZE(str_ost_entity_type); i++) {
+		if (sysfs_streq(str, str_ost_entity_type[i]))
+			return i;
+	}
+
+	return 0;
+}
+
+static ssize_t
+ost_t_policy_entity_store(struct config_item *item, const char *page,
+			  size_t count)
+{
+	struct mutex *mutexp = &item->ci_group->cg_subsys->su_mutex;
+	struct ost_policy_node *pn = to_pdrv_policy_node(item);
+	int i;
+
+	i = entity_index(page);
+	if (i) {
+		mutex_lock(mutexp);
+		pn->entity_type = i;
+		mutex_unlock(mutexp);
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+CONFIGFS_ATTR(ost_t_policy_, entity);
+
+static struct configfs_attribute *ost_t_policy_attrs[] = {
+	&ost_t_policy_attr_entity,
+	NULL,
+};
+
+static ssize_t
+notrace ost_write(struct stm_data *data, struct stm_output *output,
+		  unsigned int chan, const char *buf, size_t count,
+		  struct stm_source_data *source)
+{
+	struct ost_output *op = output->pdrv_private;
+	unsigned int c = output->channel + chan;
+	unsigned int m = output->master;
+	const unsigned char nil = 0;
+	u32 header = DATA_HEADER;
+	struct trc_hdr {
+		u16 version;
+		u16 magic;
+		u32 cpu;
+		u64 timestamp;
+		u64 tgid;
+	} hdr;
+	ssize_t sz;
+
+	/*
+	 * Identify the source by entity type.
+	 * If entity type is not set, return error value.
+	 */
+	if (op->node.entity_type)
+		header |= (ost_entity_value[op->node.entity_type] << OST_FIELD_ENTITY);
+	else
+		return -EINVAL;
+
+	/*
+	 * STP framing rules for OST frames:
+	 *   * the first packet of the OST frame is marked;
+	 *   * the last packet is a FLAG with timestamped tag.
+	 */
+	/* Message layout: HEADER / DATA / TAIL */
+	/* HEADER */
+	sz = data->packet(data, m, c, STP_PACKET_DATA, STP_PACKET_MARKED,
+			  4, (u8 *)&header);
+	if (sz <= 0)
+		return sz;
+
+	/* DATA */
+	hdr.version	= STM_MAKE_VERSION(0, 3);
+	hdr.magic	= STM_HEADER_MAGIC;
+	hdr.cpu		= raw_smp_processor_id();
+	hdr.timestamp	= sched_clock();
+	hdr.tgid	= task_tgid_nr(current);
+	sz = stm_data_write(data, m, c, false, &hdr, sizeof(hdr));
+	if (sz <= 0)
+		return sz;
+
+	sz = stm_data_write(data, m, c, false, buf, count);
+
+	/* TAIL */
+	if (sz > 0)
+		data->packet(data, m, c, STP_PACKET_FLAG,
+			STP_PACKET_TIMESTAMPED, 0, &nil);
+
+	return sz;
+}
+
+static const struct stm_protocol_driver ost_pdrv = {
+	.owner			= THIS_MODULE,
+	.name			= "p_ost",
+	.priv_sz		= sizeof(struct ost_policy_node),
+	.write			= ost_write,
+	.policy_attr		= ost_t_policy_attrs,
+	.output_open		= ost_output_open,
+	.output_close		= ost_output_close,
+	.policy_node_init	= ost_policy_node_init,
+};
+
+static int ost_stm_init(void)
+{
+	return stm_register_protocol(&ost_pdrv);
+}
+module_init(ost_stm_init);
+
+static void ost_stm_exit(void)
+{
+	stm_unregister_protocol(&ost_pdrv);
+}
+module_exit(ost_stm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MIPI Open System Trace STM framing protocol driver");

---
base-commit: 80dd246accce631c328ea43294e53b2b2dd2aa32
change-id: 20260521-stm_p_ost-3489f42a9e8c

Best regards,
-- 
Yingchao Deng <yingchao.deng@oss.qualcomm.com>


^ permalink raw reply related


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